/*
 * Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
 * All rights reserved.
 *
 * This file is part of Harmattan SmashMiner OpenGL game application.
 * 
 * Harmattan SmashMiner OpenGL Example Application version 1.0.0
 *  
 * Latest update: 15.4.2011
 *
 * The Harmattan SmashMiner OpenGL example application demonstrates how to use 
 * the OpenGL ES in Harmattan devices.
 *
 * This example is provided as a starting point for 3rd party
 * developers (you) to ease the implementation of OpenGL based 
 * games. Ideas, parts of code or methodologies that this
 * example application uses can be freely used without restrictions.
 *
 * See file README.txt how to build and compile this example application
 * in the Harmattan SDK environment. 
 * 
 * See file INSTALL.txt to find out what is required to run this
 * application and how to install the application to the device or
 * alternatively in QEMU-emulator.
 *
 * The LICENSE.txt file explains the license this software is provided
 * with. A copy of the same license is included in this source file.
 * 
 */

/*
 * Copyright (C) 2011 by Nokia Corporation.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

/// \file gamewidget_GLes2.cpp

#include "gamewidget.h"

#ifdef SMASHMINER_CONFIG_USE_OPENGL_ES_2

#include "spacecraft.h"
#include "gameengine.h"
#include "timermanager.h"
#include "visualeffectdata.h"
#include "asteroid.h"
#include "meteorshower.h"
#include "blackhole.h"
#include "comet.h"
#include "collectible.h"
#include "ufo.h"
#include "missile.h"
#include "gamelevel.h"

#ifdef SMASHMINER_CONFIG_ENABLE_HYPERJUMP_DESTINATION_VISUALIZATION
#include "hyperjumpnavigator.h"
#endif

#include <QMatrix4x4>

/*! \def FRAME_TEXTURE_Z_POS
  Z position for gamescreen control and information textures. 
  For example ship mode button and game score.
*/
#define FRAME_TEXTURE_Z_POS     +75.0f

/*! \def FRAME_BIG_TEXTURE_SIZE
  Size of big gamescreen textures. For example Game Over and Level texts.
*/
#define FRAME_BIG_TEXTURE_SIZE  +35.0f

/*! Sets the background picture for \a level.
 *  this method is a public slot, which may be
 *  launched even when the widget is not active
 *
 *  Check widget validity and set the OpenGL
 *  current rendering context by calling
 *  \c GameWidget::makeCurrent().
 */
void GameWidget::setBackGroundImage(int level)
{
    if (!isValid())
    return;

    makeCurrent();

    int tmpLevel = level - 1;
    if (tmpLevel < 0)
        tmpLevel = 0;

    tmpLevel = (tmpLevel % 20) + 1;

    GLes2LoadBackGroundTexture(tmpLevel);
}

/*!
    Initializes OpenGL ES 2 drawing.
    This method will:
    - Initiates view port. See \c GameWidget::GLes2SetupViewPort().
    - Sets OpenGL initial settings
    - Sets lighting
    - Construct, define and compile GLSL shader programs.
    - Initializes textures
    - Initializes game objects graphics. See \c GameWidget::initObjects().
*/
void GameWidget::GLes2Initialize()
{
    GLes2SetupViewPort();

    // set some opengl initial settings.
    //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

    glFrontFace(GL_CW);     // define clockwise-wound polygons as front-facing.

    lightAmbient.setX(0.4f);
    lightAmbient.setY(0.4f);
    lightAmbient.setZ(0.4f);
    lightAmbient.setW(1.0f);

    lightDiffuse.setX(0.5f);
    lightDiffuse.setY(0.5f);
    lightDiffuse.setZ(0.5f);
    lightDiffuse.setW(1.0f);

    GLfloat tmp_pos[] = { +0.4f, +0.6f, -1.0f };
    GameEngine::normalizeVect3D_GLfloat(tmp_pos);

    lightPosition.setX(tmp_pos[0]);
    lightPosition.setY(tmp_pos[1]);
    lightPosition.setZ(tmp_pos[2]);

    glDepthFunc(GL_LESS);
    glEnable(GL_DEPTH_TEST);

    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    GLfloat range[2];
    glGetFloatv(GL_ALIASED_POINT_SIZE_RANGE, range);

    // create vertex shaders.
    //^^^^^^^^^^^^^^^^^^^^^^^^
    // futher information about the lighting models used:
    // http://www.lighthouse3d.com/opengl/glsl/index.php?lights

    vertexShaderSimple = new QGLShader(QGLShader::Vertex, this);
    const char * vsrcSimple =
    "attribute highp vec4 vertex;\n"
    "uniform mediump mat4 matrix;\n"
    "void main(void)\n"
    "{\n"
    "    // compute the screen position.\n"
    "    gl_Position = matrix * vertex;\n"
    "}\n";
    GLes2CompileGLSL(vertexShaderSimple, vsrcSimple, "vertexShaderSimple");

    vertexShaderBackground = new QGLShader(QGLShader::Vertex, this);
    const char * vsrcBackGround =
    "attribute highp vec4 vertex;\n"
    "attribute highp vec4 texcoord;\n"
    "uniform mediump mat4 matrix;\n"
    "varying highp vec4 texc;\n"
    "void main(void)\n"
    "{\n"
    "    // compute the screen position.\n"
    "    gl_Position = matrix * vertex;\n"
    "    // pass the texture coordinates.\n"
    "    texc = texcoord;\n"
    "}\n";
    GLes2CompileGLSL(vertexShaderBackground, vsrcBackGround, "vertexShaderBackground");

    vertexShaderBackgroundEffect2D = new QGLShader(QGLShader::Vertex, this);
    const char * vsrcBackGroundEff2D =
    "attribute highp vec4 vertex;\n"
    "attribute highp vec4 texcoord;\n"
    "uniform mediump mat4 matrix;\n"
    "uniform mediump vec4 bombdata;\n"      // bomb X,Y positions and shockwave and light radius
    "uniform mediump vec3 bhdata3;\n"       // blackhole X,Y positions and radius ^ 2
    "varying highp vec4 texc;\n"

            "vec4 tmp_vertex;\n"

            "void bombeffect(void)\n"
            "{\n"
            "    float strength = 0.5;\n"
            "    float width = -0.005;\n"
            "    float lwidth = -0.0005;\n"
            "    float rad = bombdata[2];\n"
            
            "    // shift between vertex and bomb center.\n"
            "    float shift_x = bombdata[0] - tmp_vertex[0];\n"
            "    float shift_y = bombdata[1] - tmp_vertex[1];\n"

            "    // vertex distance from bomb center.\n"
            "    float dist = sqrt(shift_x * shift_x + shift_y * shift_y);\n"

            "    // y = a * exp(b * (x-c) * (x-c))\n"
            "    float effect = strength * exp(width * (dist-rad) * (dist-rad));\n"
            "    float leffect = strength * exp(lwidth * (dist-bombdata[3]) * (dist-bombdata[3]));\n"
            "    texc[3] = sqrt(leffect);\n"

            "    tmp_vertex[0] = tmp_vertex[0] * (1.0 + effect/2.0);\n"
            "    tmp_vertex[1] = tmp_vertex[1] * (1.0 + effect/2.0);\n"
            "}\n"

            "void blackhole(void)\n"
            "{\n"
            "    float shift_x = bhdata3[0] - tmp_vertex[0];\n"
            "    float shift_y = bhdata3[1] - tmp_vertex[1];\n"
            "    float dist2 = shift_x * shift_x + shift_y * shift_y + 1.0;\n"
            "    float eff2 = bhdata3[2] / dist2;\n"
            "    float eff_final = min(eff2, 1.0);\n"
            "    tmp_vertex[0] = tmp_vertex[0] + shift_x * eff_final;\n"
            "    tmp_vertex[1] = tmp_vertex[1] + shift_y * eff_final;\n"
            "}\n"

    "void main(void)\n"
    "{\n"
    "    // pass the texture coordinates.\n"
    "    texc = texcoord;\n"
    "    texc[3] = 0.0;\n"

            "// copy the initial vertex position.\n"
            "tmp_vertex = vec4(vertex);\n"

            "// do the bomb effect if requested.\n"
            "if (bombdata[3] > -999.0) bombeffect();\n"

            "// do the blackhole effect if requested.\n"
            "if (bhdata3[2] > -999.0 && vertex[2] > -41.0) blackhole();\n"

    "    // compute the screen position using tmp_vertex.\n"
    "    gl_Position = matrix * tmp_vertex;\n"
    "}\n";
    GLes2CompileGLSL(vertexShaderBackgroundEffect2D, vsrcBackGroundEff2D, 
        "vertexShaderBackgroundEffect2D");

    vertexShaderConstantMaterial = new QGLShader(QGLShader::Vertex, this);
    const char * vsrcConstantMaterial =
    "attribute highp vec4 vertex;\n"
    "attribute mediump vec3 normal;\n"      // the normals must be unit vectors!
    "uniform mediump mat4 matrix;\n"
    "uniform mediump vec4 l_m_ambient;\n"   // pre-multiplied light * material vector for ambient
    "uniform mediump vec4 l_m_diffuse;\n"   // pre-multiplied light * material vector for diffuse
    "uniform mediump vec3 light_dir;\n"     // light_dir must be a unit vector!
    "varying mediump vec4 tmp_color;\n"
    "void main(void)\n"
    "{\n"
    "    // compute ambient + diffuse light effects.\n"
    "    float NdotL = dot(normal, light_dir);\n"
    "    tmp_color = NdotL * l_m_diffuse + l_m_ambient;\n"
    "    // compute the screen position.\n"
    "    gl_Position = matrix * vertex;\n"
    "}\n";
    GLes2CompileGLSL(vertexShaderConstantMaterial, vsrcConstantMaterial, 
        "vertexShaderConstantMaterial");

    vertexShaderConstantMaterialEffect3D = new QGLShader(QGLShader::Vertex, this);
    const char * vsrcConstantMaterialEff3D =
    "attribute highp vec4 vertex;\n"
    "attribute mediump vec3 normal;\n"      // the normals must be unit vectors!
    "uniform mediump mat4 matrix;\n"
    "uniform mediump vec4 l_m_ambient;\n"   // pre-multiplied light * material vector for ambient
    "uniform mediump vec4 l_m_diffuse;\n"   // pre-multiplied light * material vector for diffuse
    "uniform mediump vec3 light_dir;\n"     // light_dir must be a unit vector!
    "uniform mediump vec4 bhdata4;\n"       // blackhole X,Y,Z positions and radius ^ 2
    "varying mediump vec4 tmp_color;\n"
    "void main(void)\n"
    "{\n"
    "    // compute ambient + diffuse light effects.\n"
    "    float NdotL = dot(normal, light_dir);\n"
    "    tmp_color = NdotL * l_m_diffuse + l_m_ambient;\n"
    "    // begin the blackhole effect : result written to tmp_vertex.\n"
    "    vec4 tmp_vertex;\n"
    "    float shift_x = bhdata4[0] - vertex[0];\n"
    "    float shift_y = bhdata4[1] - vertex[1];\n"
    "    float shift_z = bhdata4[2] - vertex[2];\n"
    "    float dist2 = shift_x * shift_x + shift_y * shift_y + shift_z * shift_z + 1.0;\n"
    "    float eff2 = bhdata4[3] / dist2;\n"
    "    float eff_final = min(eff2, 1.0);\n"
    "    tmp_vertex[0] = vertex[0] + shift_x * eff_final;\n"
    "    tmp_vertex[1] = vertex[1] + shift_y * eff_final;\n"
    "    tmp_vertex[2] = vertex[2] + shift_z * eff_final;\n"
    "    tmp_vertex[3] = vertex[3];\n"
    "    // compute the screen position using tmp_vertex.\n"
    "    gl_Position = matrix * tmp_vertex;\n"
    "}\n";
    GLes2CompileGLSL(vertexShaderConstantMaterialEffect3D, vsrcConstantMaterialEff3D, 
        "vertexShaderConstantMaterialEffect3D");

    vertexShaderConstantMaterialSpecular = new QGLShader(QGLShader::Vertex, this);

    // vshaderConstantMaterialSpecular implements the Phong shading.
    //
    // Phong shading is equated as follows:
    //
    // If = Ia + Id + Is 
    //
    // Where:   If is the intensity of the pixel final color
    //          Ia is the intensity of the ambient color
    //          Id is the intensity of the diffuse color
    //          Is is the intensity of the specular color
    //
    // Ia, Id and Is are all four-dimensional RGBA vectors.
    //
    // Ia = (Al * Am)
    //
    // Where:   Al is the ambient component of the light
    //          Am is the ambient component of the material
    //
    // Ia will be pre-computed for the shader into l_m_ambient.
    //
    // Id = Dl * Dm * LambertTerm
    //
    // Where:   Dl is the diffuse component of the light
    //          Dm the diffuse component of the material
    //
    // "Dl * Dm" will be pre-computed into l_m_diffuse.
    //          
    // LambertTerm = dot(N, L)
    //
    // Where:   N is the normal vector to the considered pixel
    //          L is the light vector to the considered pixel
    //
    // Is = Sm x Sl x pow( max(R dot E, 0.0), f )
    //
    // Where:   Sm is the specular component of the material
    //          Sl is the specular component of the light
    //          E is the view vector or camera vector 
    //          f is the specular exponential factor
    //            (hardness and the precision of the specular reflexion)
    //          R is the reflected light vector
    //
    // R = reflect(-L, N)

    const char * vsrcConstantMaterialSpec =
    "attribute highp vec4 vertex;\n"
    "attribute mediump vec3 normal;\n"      // the normals must be unit vectors!
    "uniform mediump mat4 matrix;\n"
    "uniform mediump vec4 l_m_ambient;\n"   // pre-multiplied light * material vector for ambient
    "uniform mediump vec4 l_m_diffuse;\n"   // pre-multiplied light * material vector for diffuse
    "uniform mediump vec3 light_dir;\n"     // light_dir must be a unit vector!
    "varying mediump vec4 tmp_color;\n"
    "void main(void)\n"
    "{\n"
    "    float LambertTerm = dot(normal, light_dir);\n"
    "    tmp_color = l_m_ambient;\n"
    "    if (LambertTerm > 0.0) {\n"
    "       tmp_color += LambertTerm * l_m_diffuse;\n"
    "       vec3 E = vec3(0.0, 0.0, -1.0);\n"
    "       vec3 R = reflect(-light_dir, normal);\n"
    "       // 'specular' contains the specular component of material and light (Sm x Sl)\n"
    "       // adjust this if you want other than white colour effect for the specular light.\n"
    "       vec4 specular = vec4(1.0, 1.0, 1.0, 1.0);\n"
    "       tmp_color += specular * pow(max(dot(R,E), 0.0), 6.0);\n"
    "    }\n"
    "    tmp_color = clamp(tmp_color, 0.0, 1.0);\n"
    "    // compute the screen position.\n"
    "    gl_Position = matrix * vertex;\n"
    "}\n";
    GLes2CompileGLSL(vertexShaderConstantMaterialSpecular, vsrcConstantMaterialSpec,
        "vertexShaderConstantMaterialSpecular");

    vertexShaderConstantMaterialSpecularEffect3D = new QGLShader(QGLShader::Vertex, this);
    const char * vsrcConstantMaterialSpecEff3D =
    "attribute highp vec4 vertex;\n"
    "attribute mediump vec3 normal;\n"      // the normals must be unit vectors!
    "uniform mediump mat4 matrix;\n"
    "uniform mediump vec4 l_m_ambient;\n"   // pre-multiplied light * material vector for ambient
    "uniform mediump vec4 l_m_diffuse;\n"   // pre-multiplied light * material vector for diffuse
    "uniform mediump vec3 light_dir;\n"     // light_dir must be a unit vector!
    "uniform mediump vec4 bhdata4;\n"       // blackhole X,Y,Z positions and radius ^ 2
    "varying mediump vec4 tmp_color;\n"
    "void main(void)\n"
    "{\n"
    "    // compute ambient + diffuse light effects.\n"
    "    float LambertTerm = dot(normal, light_dir);\n"
    "    tmp_color = l_m_ambient;\n"
    "    if (LambertTerm > 0.0) {\n"
    "       tmp_color += LambertTerm * l_m_diffuse;\n"
    "       vec3 E = vec3(0.0, 0.0, -1.0);\n"
    "       vec3 R = reflect(-light_dir, normal);\n"
    "       // 'specular' contains the specular component of material and light (Sm x Sl)\n"
    "       // adjust this if you want other than white colour effect for the specular light.\n"
    "       vec4 specular = vec4(1.0, 1.0, 1.0, 1.0);\n"
    "       tmp_color += specular * pow(max(dot(R,E), 0.0), 6.0);\n"
    "    }\n"
    "    // begin the blackhole effect : result written to tmp_vertex.\n"
    "    vec4 tmp_vertex;\n"
    "    float shift_x = bhdata4[0] - vertex[0];\n"
    "    float shift_y = bhdata4[1] - vertex[1];\n"
    "    float shift_z = bhdata4[2] - vertex[2];\n"
    "    float dist2 = shift_x * shift_x + shift_y * shift_y + shift_z * shift_z + 1.0;\n"
    "    float eff2 = bhdata4[3] / dist2;\n"
    "    float eff_final = min(eff2, 1.0);\n"
    "    tmp_vertex[0] = vertex[0] + shift_x * eff_final;\n"
    "    tmp_vertex[1] = vertex[1] + shift_y * eff_final;\n"
    "    tmp_vertex[2] = vertex[2] + shift_z * eff_final;\n"
    "    tmp_vertex[3] = vertex[3];\n"
    "    // compute the screen position using tmp_vertex.\n"
    "    gl_Position = matrix * tmp_vertex;\n"
    "}\n";
    GLes2CompileGLSL(vertexShaderConstantMaterialSpecularEffect3D, vsrcConstantMaterialSpecEff3D,
        "vertexShaderConstantMaterialSpecularEffect3D");

    vertexShaderVariableMaterial = new QGLShader(QGLShader::Vertex, this);
    const char *vsrcVariableMaterial =
    "attribute highp vec4 vertex;\n"
    "attribute mediump vec3 normal;\n"      // the normals must be unit vectors!
    "attribute mediump vec3 material;\n"
    "uniform mediump mat4 matrix;\n"
    "uniform mediump vec4 l_m_ambient;\n"   // pre-multiplied light * material vector for ambient
    "uniform mediump vec4 l_m_diffuse;\n"   // pre-multiplied light * material vector for diffuse
    "uniform mediump vec3 light_dir;\n"     // light_dir must be a unit vector!
    "varying mediump vec4 tmp_color;\n"
    "void main(void)\n"
    "{\n"
    "    // compute ambient + diffuse light effects.\n"
    "    vec4 material4 = vec4(material[0], material[1], material[2], 1.0);\n"
    "    vec4 c_ambient = l_m_ambient * material4;\n"
    "    vec4 c_diffuse = l_m_diffuse * material4;\n"
    "    float NdotL = dot(normal, light_dir);\n"
    "    tmp_color = NdotL * c_diffuse + c_ambient;\n"
    "    // compute the screen position.\n"
    "    gl_Position = matrix * vertex;\n"
    "}\n";
    GLes2CompileGLSL(vertexShaderVariableMaterial,  vsrcVariableMaterial, 
        "vertexShaderVariableMaterial");

    vertexShaderVariableMaterialEffect3D = new QGLShader(QGLShader::Vertex, this);
    const char * vsrcVariableMaterialEff3D =
    "attribute highp vec4 vertex;\n"
    "attribute mediump vec3 normal;\n"      // the normals must be unit vectors!
    "attribute mediump vec3 material;\n"
    "uniform mediump mat4 matrix;\n"
    "uniform mediump vec4 l_m_ambient;\n"   // pre-multiplied light * material vector for ambient
    "uniform mediump vec4 l_m_diffuse;\n"   // pre-multiplied light * material vector for diffuse
    "uniform mediump vec3 light_dir;\n"     // light_dir must be a unit vector!
    "uniform mediump vec4 bhdata4;\n"       // blackhole X,Y,Z positions and radius ^ 2
    "varying mediump vec4 tmp_color;\n"
    "void main(void)\n"
    "{\n"
    "    // compute ambient + diffuse light effects.\n"
    "    vec4 material4 = vec4(material[0], material[1], material[2], 1.0);\n"
    "    vec4 c_ambient = l_m_ambient * material4;\n"
    "    vec4 c_diffuse = l_m_diffuse * material4;\n"
    "    float NdotL = dot(normal, light_dir);\n"
    "    tmp_color = NdotL * c_diffuse + c_ambient;\n"
    "    // begin the blackhole effect : result written to tmp_vertex.\n"
    "    vec4 tmp_vertex;\n"
    "    float shift_x = bhdata4[0] - vertex[0];\n"
    "    float shift_y = bhdata4[1] - vertex[1];\n"
    "    float shift_z = bhdata4[2] - vertex[2];\n"
    "    float dist2 = shift_x * shift_x + shift_y * shift_y + shift_z * shift_z + 1.0;\n"
    "    float eff2 = bhdata4[3] / dist2;\n"
    "    float eff_final = min(eff2, 1.0);\n"
    "    tmp_vertex[0] = vertex[0] + shift_x * eff_final;\n"
    "    tmp_vertex[1] = vertex[1] + shift_y * eff_final;\n"
    "    tmp_vertex[2] = vertex[2] + shift_z * eff_final;\n"
    "    tmp_vertex[3] = vertex[3];\n"
    "    // compute the screen position using tmp_vertex.\n"
    "    gl_Position = matrix * tmp_vertex;\n"
    "}\n";
    GLes2CompileGLSL(vertexShaderVariableMaterialEffect3D, vsrcVariableMaterialEff3D, 
        "vertexShaderVariableMaterialEffect3D");

    vertexShaderParticles = new QGLShader(QGLShader::Vertex, this);
    const char * vsrcParticles =
    "attribute highp vec4 vertex;\n"
    "attribute mediump vec3 pdata;\n"
    "uniform mediump mat4 matrix;\n"
    "varying mediump float alpha;\n"
    "void main(void)\n"
    "{\n"
    "    alpha = pdata[1];\n"
    "    float size = pdata[0];\n"
    "    if (size < 0.0) size = -size;"
    "    gl_PointSize = size;\n"
    "    // compute the screen position.\n"
    "    gl_Position = matrix * vertex;\n"
    "}\n";
    GLes2CompileGLSL(vertexShaderParticles, vsrcParticles, "vertexShaderParticles");

    vertexShaderParticlesEffect2D = new QGLShader(QGLShader::Vertex, this);
    const char * vsrcParticlesEff2D =
    "attribute highp vec4 vertex;\n"
    "attribute mediump vec3 pdata;\n"
    "uniform mediump mat4 matrix;\n"
    "uniform mediump vec3 bhdata3;\n"       // blackhole X,Y positions and radius ^ 2
    "varying mediump float alpha;\n"
    "void main(void)\n"
    "{\n"
    "    alpha = pdata[1];\n"
    "    float size = pdata[0];\n"
    "    if (size < 0.0) size = -size;"
    "    gl_PointSize = size;\n"
    "    // begin the blackhole effect : result written to tmp_vertex.\n"
    "    vec4 tmp_vertex;\n"
    "    float shift_x = bhdata3[0] - vertex[0];\n"
    "    float shift_y = bhdata3[1] - vertex[1];\n"
    "    float dist2 = shift_x * shift_x + shift_y * shift_y + 1.0;\n"
    "    float eff2 = bhdata3[2] / dist2;\n"
    "    float eff_final = min(eff2, 1.0);\n"
    // a blackhole/hyperjump particle (one with a negative radius) is not shifted.
    "    if (pdata[0] < 0.0) eff_final = 0.0;\n"
    "    tmp_vertex[0] = vertex[0] + shift_x * eff_final;\n"
    "    tmp_vertex[1] = vertex[1] + shift_y * eff_final;\n"
    "    tmp_vertex[2] = vertex[2];\n"
    "    tmp_vertex[3] = vertex[3];\n"
    "    // compute the screen position using tmp_vertex.\n"
    "    gl_Position = matrix * tmp_vertex;\n"
    "}\n";
    GLes2CompileGLSL(vertexShaderParticlesEffect2D, vsrcParticlesEff2D, 
                     "vertexShaderParticlesEffect2D");

    vertexShaderTexture = new QGLShader(QGLShader::Vertex, this);
    const char * vsrcTexture =
    "attribute highp vec4 vertex;\n"
    "attribute highp vec4 texcoord;\n"
    "uniform mediump mat4 matrix;\n"
    "varying highp vec4 texc;\n"
    "void main(void)\n"
    "{\n"
    "    // compute the screen position.\n"
    "    gl_Position = matrix * vertex;\n"
    "    // pass the texture coordinates.\n"
    "    texc = texcoord;\n"
    "}\n";
    GLes2CompileGLSL(vertexShaderTexture, vsrcTexture, "vertexShaderTexture");

    vertexShaderTextureEffect3D = new QGLShader(QGLShader::Vertex, this);
    const char * vsrcTextureEff3D =
    "attribute highp vec4 vertex;\n"
    "attribute highp vec4 texcoord;\n"
    "uniform mediump mat4 matrix;\n"
    "uniform mediump vec4 bhdata4;\n"       // blackhole X,Y,Z positions and radius ^ 2
    "varying highp vec4 texc;\n"
    "void main(void)\n"
    "{\n"
    "    // begin the blackhole effect : result written to tmp_vertex.\n"
    "    vec4 tmp_vertex;\n"
    "    float shift_x = bhdata4[0] - vertex[0];\n"
    "    float shift_y = bhdata4[1] - vertex[1];\n"
    "    float shift_z = bhdata4[2] - vertex[2];\n"
    "    float dist2 = shift_x * shift_x + shift_y * shift_y + shift_z * shift_z + 1.0;\n"
    "    float eff2 = bhdata4[3] / dist2;\n"
    "    float eff_final = min(eff2, 1.0);\n"
    "    tmp_vertex[0] = vertex[0] + shift_x * eff_final;\n"
    "    tmp_vertex[1] = vertex[1] + shift_y * eff_final;\n"
    "    tmp_vertex[2] = vertex[2] + shift_z * eff_final;\n"
    "    tmp_vertex[3] = vertex[3];\n"
    "    // compute the screen position.\n"
    "    gl_Position = matrix * tmp_vertex;\n"
    "    // pass the texture coordinates.\n"
    "    texc = texcoord;\n"
    "}\n";
    GLes2CompileGLSL(vertexShaderTextureEffect3D, vsrcTextureEff3D, "vertexShaderTextureEffect3D");

    // create fragment shaders.
    //^^^^^^^^^^^^^^^^^^^^^^^^^^

    fragmentShaderRedOnly = new QGLShader(QGLShader::Fragment, this);
    const char * fsrc_RedOnly =
    "void main(void)\n"
    "{\n"
    "    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
    "}\n";
    GLes2CompileGLSL(fragmentShaderRedOnly, fsrc_RedOnly, "fragmentShaderRedOnly");

    fragmentShaderGreenOnly = new QGLShader(QGLShader::Fragment, this);
    const char * fsrc_GreenOnly =
    "void main(void)\n"
    "{\n"
    "    gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);\n"
    "}\n";
    GLes2CompileGLSL(fragmentShaderGreenOnly, fsrc_GreenOnly, "fragmentShaderGreenOnly");

    fragmentShaderBlueOnly = new QGLShader(QGLShader::Fragment, this);
    const char * fsrc_BlueOnly =
    "void main(void)\n"
    "{\n"
    "    gl_FragColor = vec4(0.0, 0.0, 1.0, 1.0);\n"
    "}\n";
    GLes2CompileGLSL(fragmentShaderBlueOnly, fsrc_BlueOnly, "fragmentShaderBlueOnly");

    fragmentShaderBackGround = new QGLShader(QGLShader::Fragment, this);
    const char *fsrcBackGround =
    "uniform sampler2D tsampler;\n"
    "varying highp vec4 texc;\n"
    "void main(void)\n"
    "{\n"
    "    gl_FragColor = texture2D(tsampler, texc.st);\n"
    "}\n";
    GLes2CompileGLSL(fragmentShaderBackGround, fsrcBackGround, "fragmentShaderBackGround");

    fragmentShaderBackGroundEffect = new QGLShader(QGLShader::Fragment, this);
    const char *fsrcBackGroundEff =
    "uniform sampler2D tsampler;\n"
    "varying highp vec4 texc;\n"
    "void main(void)\n"
    "{\n"
    "    highp vec3 color = texture2D(tsampler, texc.st).rgb;\n"
    "    color[0] = color[0] + texc[3];\n"
    "    color[1] = color[1] + texc[3];\n"
    "    color[2] = color[2] + texc[3];\n"
    "    gl_FragColor = vec4(clamp(color, 0.0, 1.0), 1.0);\n"
    "}\n";
    GLes2CompileGLSL(fragmentShaderBackGroundEffect, fsrcBackGroundEff, "fragmentShaderBackGroundEffect");

    fragmentShaderMaterial = new QGLShader(QGLShader::Fragment, this);
    const char * fsrc_Material =
    "varying mediump vec4 tmp_color;\n"
    "void main(void)\n"
    "{\n"
    "    gl_FragColor = tmp_color;\n"
    "}\n";
    GLes2CompileGLSL(fragmentShaderMaterial, fsrc_Material, "fragmentShaderMaterial");

    fragmentShaderParticles = new QGLShader(QGLShader::Fragment, this);
    const char * fsrcParticles =
    "varying mediump float alpha;\n"
    "void main(void)\n"
    "{\n"
    "    gl_FragColor = vec4(1.0, 1.0, 1.0, alpha);\n"
    "}\n";
    GLes2CompileGLSL(fragmentShaderParticles, fsrcParticles, "fragmentShaderParticles");

    fragmentShaderTexture = new QGLShader(QGLShader::Fragment, this);
    const char *fsrcTexture =
    "uniform sampler2D tsampler;\n"
    "varying highp vec4 texc;\n"
    "void main(void)\n"
    "{\n"
    "    gl_FragColor = texture2D(tsampler, texc.st);\n"
    "}\n";
    GLes2CompileGLSL(fragmentShaderTexture, fsrcTexture, "fragmentShaderTexture");

    // initialize the textures and background.
    //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

    GLes2InitBackGround();

    // the textures...
    //^^^^^^^^^^^^^^^^^

    glGenTextures(NumberOfTextures, textureNames);

    // GLes2LoadBackGroundTexture() must be called after
    // GLes2InitBackGround() which initializes the data storage.

    GLes2LoadBackGroundTexture(engine->commandlineStartLevel > 0 ? engine->commandlineStartLevel : 1);  // level = 1

    GLes2InitTextureBomb();
    GLes2InitTextureHyperJump();
    GLes2InitTextureNumbers();
    GLes2InitTextureBigNumbers();
    GLes2InitTextureLetterX();
    GLes2InitTextureLetterColon();
    GLes2InitTextureMiner();
    GLes2InitTextureRewindR();
    GLes2InitTextureTime();
    GLes2InitTextureGetReady();
    GLes2InitTextureLevel();
    GLes2InitTextureLevelSmall();
    GLes2InitTextureGameOver();
    GLes2InitTexturePaused();
    GLes2InitTextureButtonPause();
    GLes2InitTextureButtonPlay();
    GLes2InitTextureButtonModeBall();
    GLes2InitTextureButtonModeCone();
    GLes2InitTextureButtonBomb();

    GLes2InitCometTexture();

    // initObjects() must be called after
    // GLes2_setupViewPort() but before creating the shader programs.

    initObjects();

    // create the shader programs.
    //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

    programBackground = new spBackGround(vertexShaderBackground, fragmentShaderBackGround);
    programBackgroundEffect2D = new spBackGroundEff2D(vertexShaderBackgroundEffect2D, fragmentShaderBackGroundEffect);

    programConstantMaterial = new spConstantMaterial(vertexShaderConstantMaterial, fragmentShaderMaterial);
    programConstanMaterialEffect3D = new spConstantMaterialEff3D(vertexShaderConstantMaterialEffect3D, fragmentShaderMaterial);

    programConstantMaterialSpecular = new spConstantMaterial(vertexShaderConstantMaterialSpecular, fragmentShaderMaterial);
    programConstantMaterialSpecularEffect3D = new spConstantMaterialEff3D(vertexShaderConstantMaterialSpecularEffect3D, fragmentShaderMaterial);

    programVariableMaterial = new spVariableMaterial(vertexShaderVariableMaterial, fragmentShaderMaterial);
    programVariableMaterial3D = new spVariableMaterialEff3D(vertexShaderVariableMaterialEffect3D, fragmentShaderMaterial);

    programParticles = new spParticles(vertexShaderParticles, fragmentShaderParticles);
    programParticlesEffect2D = new spParticlesEff2D(vertexShaderParticlesEffect2D, fragmentShaderParticles);

    programTexture = new spTexture(vertexShaderTexture, fragmentShaderTexture);
    programTextureEffects3D = new spTextureEff3D(vertexShaderTextureEffect3D, fragmentShaderTexture);

#ifdef SMASHMINER_CONFIG_ENABLE_CONTROL_GAUGES

    programControlCauges = new spControlGauges(vertexShaderSimple, fragmentShaderGreenOnly);

#ifdef SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE

    programRewindCauge = new spRewindGauge(vertexShaderSimple, fragmentShaderRedOnly);

#endif  // SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE
#endif  // SMASHMINER_CONFIG_ENABLE_CONTROL_GAUGES

}

/*!
  Initializes view port for OpenGL ES 2.
  Sets up screen width and height and an orthographic projection.
  Ortographic projection is used since it is faster and we don't really need perspective in a 2D game.
*/
void GameWidget::GLes2SetupViewPort()
{
    setViewRanges();

    glViewport(0, 0, engine->gameScreenWidth, engine->gameScreenHeight);

    // use an orthographic projection since it is faster
    // and we don't really need perspective in a 2D game.

    pmvMatrix.setToIdentity();
    pmvMatrix.ortho(-engine->gameViewRangeX, +engine->gameViewRangeX,
                    -engine->gameViewRangeY, +engine->gameViewRangeY,
                    -engine->gameViewRangeZ, +engine->gameViewRangeZ);
}

/// Loads background texture image from resources and initiates background texture and coordinates.
void GameWidget::GLes2LoadBackGroundTexture(int level)
{
    // all background texture images have original size of
    // 1708x960 ; if this is changed, make sure the new values
    // are set into bgImageOriginalWidth and bgImageOriginalHeight.

    backgroundImageOriginalWidth = 1708;
    backgroundImageOriginalHeight = 960;

    QString imagePath;
    if (engine->commandlineUseLocalFiles)
        imagePath = "./qml/smashminer/pics/";
    else
        imagePath = "/opt/smashminer/";

    QString imageName;
    switch (level) {
    case 2:
        imageName = imagePath + "1024x512_02.jpg";
        break;
    case 3:
        imageName = imagePath + "1024x512_03.jpg";
        break;
    case 4:
        imageName = imagePath + "1024x512_04.jpg";
        break;
    case 5:
        imageName = imagePath + "1024x512_05.jpg";
        break;
    case 6:
        imageName = imagePath + "1024x512_06.jpg";
        break;
    case 7:
        imageName = imagePath + "1024x512_07.jpg";
        break;
    case 8:
        imageName = imagePath + "1024x512_08.jpg";
        break;
    case 9:
        imageName = imagePath + "1024x512_09.jpg";
        break;
    case 10:
        imageName = imagePath + "1024x512_10.jpg";
        break;
    case 11:
        imageName = imagePath + "1024x512_11.jpg";
        break;
    case 12:
        imageName = imagePath + "1024x512_12.jpg";
        break;
    case 13:
        imageName = imagePath + "1024x512_13.jpg";
        break;
    case 14:
        imageName = imagePath + "1024x512_14.jpg";
        break;
    case 15:
        imageName = imagePath + "1024x512_15.jpg";
        break;
    case 16:
        imageName = imagePath + "1024x512_16.jpg";
        break;
    case 17:
        imageName = imagePath + "1024x512_17.jpg";
        break;
    case 18:
        imageName = imagePath + "1024x512_18.jpg";
        break;
    case 19:
        imageName = imagePath + "1024x512_19.jpg";
        break;
    case 20:
        imageName = imagePath + "1024x512_20.jpg";
        break;
    default:
        imageName = imagePath + "1024x512_01.jpg";
        break;
    }

    QImage img(imageName);

    if (img.depth() != 32)
        qFatal("GameWidget::GLes2LoadBackGroundTexture() : image file not"
               "found from path '%s' "
               "Check that the files are properly installed or use the -l"
               "commandline option to read local files from the source tree.",
               imageName.toUtf8().data());

    QImage conv = QGLWidget::convertToGLFormat(img);

    glBindTexture(GL_TEXTURE_2D, textureNames[TextureIndexBG]);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, conv.width(), conv.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, conv.bits());

    // (re)compute new background image coordinates.

    GLes2InitBackGround();
}

/*! Initiates background texture and coordinates
 *
 */
void GameWidget::GLes2InitBackGround()
{
    GLfloat startX = -engine->gameViewRangeX;
    GLfloat startY = +engine->gameViewRangeY;

    GLfloat rangeX = +2.0 * engine->gameViewRangeX;
    GLfloat rangeY = -2.0 * engine->gameViewRangeY;

    GLfloat stepX = rangeX / (GLfloat) BG_GRID_X_SIZE;
    GLfloat stepY = rangeY / (GLfloat) BG_GRID_Y_SIZE;

    backgroundData2x.resize(0);

    GLfloat tmpf;

    GLfloat horizontal[BG_GRID_X_SIZE + 1];
    GLfloat vertical[BG_GRID_Y_SIZE + 1];

    // compute horizontal points.

    tmpf = startX;
    for (int i = 0;i <= BG_GRID_X_SIZE;i++) {
        horizontal[i] = tmpf;
        tmpf += stepX;
    }

    // compute vertical points.

    tmpf = startY;
    for (int i = 0;i <= BG_GRID_Y_SIZE;i++) {
        vertical[i] = tmpf;
        tmpf += stepY;
    }

    // write the vertex data for triangles.
    // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    // A---B    1st triangle = A-B-C
    // |   |    2nd triangle = C-D-A
    // D---C

    for (int x = 0;x < BG_GRID_X_SIZE;x++) {
        for (int y = 0;y < BG_GRID_Y_SIZE;y++) {
            const float zEdge = -42.0;
            const float zOther = -40.0;

            float zValue;

            int tmpX;
            int tmpY;

            tmpX = x + 0; tmpY = y + 0; zValue = zOther;
            if (tmpX == 0 || tmpX == BG_GRID_X_SIZE) zValue = zEdge;
            if (tmpY == 0 || tmpY == BG_GRID_Y_SIZE) zValue = zEdge;
            backgroundData2x.push_back(horizontal[tmpX]);
            backgroundData2x.push_back(vertical[tmpY]);
            backgroundData2x.push_back(zValue);

            tmpX = x + 1; tmpY = y + 0; zValue = zOther;
            if (tmpX == 0 || tmpX == BG_GRID_X_SIZE) zValue = zEdge;
            if (tmpY == 0 || tmpY == BG_GRID_Y_SIZE) zValue = zEdge;
            backgroundData2x.push_back(horizontal[tmpX]);
            backgroundData2x.push_back(vertical[tmpY]);
            backgroundData2x.push_back(zValue);

            tmpX = x + 1; tmpY = y + 1; zValue = zOther;
            if (tmpX == 0 || tmpX == BG_GRID_X_SIZE) zValue = zEdge;
            if (tmpY == 0 || tmpY == BG_GRID_Y_SIZE) zValue = zEdge;
            backgroundData2x.push_back(horizontal[tmpX]);
            backgroundData2x.push_back(vertical[tmpY]);
            backgroundData2x.push_back(zValue);

            tmpX = x + 1; tmpY = y + 1; zValue = zOther;
            if (tmpX == 0 || tmpX == BG_GRID_X_SIZE) zValue = zEdge;
            if (tmpY == 0 || tmpY == BG_GRID_Y_SIZE) zValue = zEdge;
            backgroundData2x.push_back(horizontal[tmpX]);
            backgroundData2x.push_back(vertical[tmpY]);
            backgroundData2x.push_back(zValue);

            tmpX = x + 0; tmpY = y + 1; zValue = zOther;
            if (tmpX == 0 || tmpX == BG_GRID_X_SIZE) zValue = zEdge;
            if (tmpY == 0 || tmpY == BG_GRID_Y_SIZE) zValue = zEdge;
            backgroundData2x.push_back(horizontal[tmpX]);
            backgroundData2x.push_back(vertical[tmpY]);
            backgroundData2x.push_back(zValue);

            tmpX = x + 0; tmpY = y + 0; zValue = zOther;
            if (tmpX == 0 || tmpX == BG_GRID_X_SIZE) zValue = zEdge;
            if (tmpY == 0 || tmpY == BG_GRID_Y_SIZE) zValue = zEdge;
            backgroundData2x.push_back(horizontal[tmpX]);
            backgroundData2x.push_back(vertical[tmpY]);
            backgroundData2x.push_back(zValue);
        }
    }

    backgroundVertexDataCount = backgroundData2x.size();

    backgroundTextureCoordinateDataStartIndex = backgroundData2x.size();
    backgroundTextureCoordinateDataCount = 0;

    // compute an aspect ratio fix for the background image.

    const GLfloat tmp1 = (GLfloat) backgroundImageOriginalWidth / (GLfloat) backgroundImageOriginalHeight;
    const GLfloat tmp2 = (GLfloat) engine->screenWidth() / (GLfloat) engine->screenHeight();

    if (tmp1 < tmp2) {
        const GLfloat aspectRatioFix = 1.0 - tmp1 / tmp2;

        startX = 0.0;
        startY = 1.0 - aspectRatioFix * 0.50;

        rangeX = +1.0;
        rangeY = -1.0 + aspectRatioFix;
    } else {
        const GLfloat aspectRatioFix = 1.0 - tmp2 / tmp1;

        startX = 0.0 + aspectRatioFix * 0.50;
        startY = 1.0;

        rangeX = +1.0 - aspectRatioFix;
        rangeY = -1.0;
    }

    stepX = rangeX / (GLfloat) BG_GRID_X_SIZE;
    stepY = rangeY / (GLfloat) BG_GRID_Y_SIZE;

    // compute horizontal points.

    tmpf = startX;
    for (int i = 0;i <= BG_GRID_X_SIZE;i++) {
        horizontal[i] = tmpf;
        tmpf += stepX;
    }

    // compute vertical points.

    tmpf = startY;
    for (int i = 0;i <= BG_GRID_Y_SIZE;i++) {
        vertical[i] = tmpf;
        tmpf += stepY;
    }

    // write the texture coordinate data for triangles.
    // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    // A---B    1st triangle = A-B-C
    // |   |    2nd triangle = C-D-A
    // D---C

    for (int x = 0;x < BG_GRID_X_SIZE;x++) {
        for (int y = 0;y < BG_GRID_Y_SIZE;y++) {
            backgroundData2x.push_back(horizontal[x + 0]);
            backgroundData2x.push_back(vertical[y + 0]);

            backgroundData2x.push_back(horizontal[x + 1]);
            backgroundData2x.push_back(vertical[y + 0]);

            backgroundData2x.push_back(horizontal[x + 1]);
            backgroundData2x.push_back(vertical[y + 1]);

            backgroundData2x.push_back(horizontal[x + 1]);
            backgroundData2x.push_back(vertical[y + 1]);

            backgroundData2x.push_back(horizontal[x + 0]);
            backgroundData2x.push_back(vertical[y + 1]);

            backgroundData2x.push_back(horizontal[x + 0]);
            backgroundData2x.push_back(vertical[y + 0]);

            backgroundTextureCoordinateDataCount += 12;
        }
    }

#ifdef SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

    if (backgroundTextureCoordinateDataStartIndex + backgroundTextureCoordinateDataCount 
            != (int) backgroundData2x.size()) {
        qFatal("GameWidget::GLes2InitBackGround() : texture coordinate array is bad.");
    }

#endif  // SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS
}

/*!
Initiates texture data.
Method does the following things:
- Converts image to OpenGL format.
- Binds texture name to OpenGL.
- Sets texture parameters.
- Specifies texture image to OpenGL
- Writes the texture coordinate data for triangles.
*/
void GameWidget::GLes2InitTexture(const QImage &image, TextureData &textureData)
{
    QImage conv = QGLWidget::convertToGLFormat(image);

    glBindTexture(GL_TEXTURE_2D, textureNames[textureData.name]);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, conv.width(), conv.height(),
                 0, GL_RGBA, GL_UNSIGNED_BYTE, conv.bits());

    // write the texture coordinate data for triangles.
    // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    // A---B    1st triangle = A-B-C
    // |   |    2nd triangle = C-D-A
    // D---C

    const GLfloat x = -textureData.width / 2.0;
    const GLfloat y = -textureData.height / 2.0;

    textureData.vertexData.push_back(x);
    textureData.vertexData.push_back(y);
    textureData.vertexData.push_back(0.0f);

    textureData.vertexData.push_back(x + textureData.width);
    textureData.vertexData.push_back(y);
    textureData.vertexData.push_back(0.0f);

    textureData.vertexData.push_back(x + textureData.width);
    textureData.vertexData.push_back(y + textureData.height);
    textureData.vertexData.push_back(0.0f);

    textureData.vertexData.push_back(x + textureData.width);
    textureData.vertexData.push_back(y + textureData.height);
    textureData.vertexData.push_back(0.0f);

    textureData.vertexData.push_back(x);
    textureData.vertexData.push_back(y + textureData.height);
    textureData.vertexData.push_back(0.0f);

    textureData.vertexData.push_back(x);
    textureData.vertexData.push_back(y);
    textureData.vertexData.push_back(0.0f);

    textureData.textureCoordinateData.push_back(0.0f);
    textureData.textureCoordinateData.push_back(0.0f);

    textureData.textureCoordinateData.push_back(1.0f);
    textureData.textureCoordinateData.push_back(0.0f);

    textureData.textureCoordinateData.push_back(1.0f);
    textureData.textureCoordinateData.push_back(1.0f);

    textureData.textureCoordinateData.push_back(1.0f);
    textureData.textureCoordinateData.push_back(1.0f);

    textureData.textureCoordinateData.push_back(0.0f);
    textureData.textureCoordinateData.push_back(1.0f);

    textureData.textureCoordinateData.push_back(0.0f);
    textureData.textureCoordinateData.push_back(0.0f);
}

/*! Initiates bomb texture data.
 *
 * Bomb texture data is stored into \c GameWidget::texBombData, instance of \c TextureData.
 */
void GameWidget::GLes2InitTextureBomb()
{
    if (texBombData != NULL)
        qFatal("GameWidget::GLes2InitTextureBomb() : exists already!");

    texBombData = new TextureData;
    texBombData->name = TextureIndexBomb;
    texBombData->width = 16.0f;
    texBombData->height = 16.0f;

    QImage img(":/images/bomb_64x64.png");
    GLes2InitTexture(img, *texBombData);
}

/// Initiates hyper jump texture data.
void GameWidget::GLes2InitTextureHyperJump()
{
    if (texHyperJumpData != NULL)
        qFatal("GameWidget::GLes2InitTextureHyperJump() : exists already!");

    texHyperJumpData = new TextureData;
    texHyperJumpData->name = TextureIndexHyperJump;
    texHyperJumpData->width = 8.0f;
    texHyperJumpData->height = 8.0f;

    QImage img(":/images/hyperjump_64x64.png");
    GLes2InitTexture(img, *texHyperJumpData);
}

/// Initiates number texture data.
void GameWidget::GLes2InitTextureNumbers()
{
    for (int i = 0;i < 10;i++) {
        TextureData ** td = NULL;
        TextureIndex name;

        switch (i) {
        case 0:
            td = &texNumber0Data;
            name = TextureIndexNumber0;
            break;
        case 1:
            td = &texNumber1Data;
            name = TextureIndexNumber1;
            break;
        case 2:
            td = &texNumber2Data;
            name = TextureIndexNumber2;
            break;
        case 3:
            td = &texNumber3Data;
            name = TextureIndexNumber3;
            break;
        case 4:
            td = &texNumber4Data;
            name = TextureIndexNumber4;
            break;
        case 5:
            td = &texNumber5Data;
            name = TextureIndexNumber5;
            break;
        case 6:
            td = &texNumber6Data;
            name = TextureIndexNumber6;
            break;
        case 7:
            td = &texNumber7Data;
            name = TextureIndexNumber7;
            break;
        case 8:
            td = &texNumber8Data;
            name = TextureIndexNumber8;
            break;
        case 9:
            td = &texNumber9Data;
            name = TextureIndexNumber9;
            break;
        default:
            qFatal("GameWidget::GLes2InitTextureNumbers() : bad value i = %d", i);
        }

        if ((*td) != NULL) {
            qFatal("GameWidget::GLes2InitTextureNumbers() : i = %d exists already!", i);
        }

        (*td) = new TextureData;
        (*td)->name = name;
        (*td)->width = 8.0f;
        (*td)->height = 8.0f;

        GLes2InitTexture(numberImages[i], **td);
    }
}

/// Initiates big numbers texture data.
void GameWidget::GLes2InitTextureBigNumbers()
{
    for (int i = 0;i < 10;i++) {
        TextureData ** td = NULL;
        TextureIndex name;

        switch (i) {
        case 0:
            td = &texBigNumber0Data;
            name = TextureIndexNumber0;
            break;
        case 1:
            td = &texBigNumber1Data;
            name = TextureIndexNumber1;
            break;
        case 2:
            td = &texBigNumber2Data;
            name = TextureIndexNumber2;
            break;
        case 3:
            td = &texBigNumber3Data;
            name = TextureIndexNumber3;
            break;
        case 4:
            td = &texBigNumber4Data;
            name = TextureIndexNumber4;
            break;
        case 5:
            td = &texBigNumber5Data;
            name = TextureIndexNumber5;
            break;
        case 6:
            td = &texBigNumber6Data;
            name = TextureIndexNumber6;
            break;
        case 7:
            td = &texBigNumber7Data;
            name = TextureIndexNumber7;
            break;
        case 8:
            td = &texBigNumber8Data;
            name = TextureIndexNumber8;
            break;
        case 9:
            td = &texBigNumber9Data;
            name = TextureIndexNumber9;
            break;
        default:
            qFatal("GameWidget::GLes2InitTextureBigNumbers() : bad value i = %d", i);
        }

        if ((*td) != NULL) {
            qFatal("GameWidget::GLes2InitTextureBigNumbers() : i = %d exists already!", i);
        }

        (*td) = new TextureData;
        (*td)->name = name;
        (*td)->width = FRAME_BIG_TEXTURE_SIZE * 0.25f;
        (*td)->height = FRAME_BIG_TEXTURE_SIZE;

        GLes2InitTexture(numberImages[i], **td);
    }
}

/// Initiates letter X texture data.
void GameWidget::GLes2InitTextureLetterX()
{
    if (texLetterXData != NULL)
        qFatal("GameWidget::GLes2InitTextureLetterX() : exists already!");

    texLetterXData = new TextureData;
    texLetterXData->name = TextureIndexLetterX;
    texLetterXData->width = 8.0f;
    texLetterXData->height = 8.0f;

    QImage img(":/images/16x32_x.png");
    GLes2InitTexture(img, *texLetterXData);
}

/// Initiates colon letter texture data.
void GameWidget::GLes2InitTextureLetterColon()
{
    if (texLetterColonData != NULL)
        qFatal("GameWidget::GLes2InitTextureLetterColon() : exists already!");

    texLetterColonData = new TextureData;
    texLetterColonData->name = TextureIndexLetterColon;
    texLetterColonData->width = 8.0f;
    texLetterColonData->height = 8.0f;

    QImage img(":/images/16x32_colon.png");
    GLes2InitTexture(img, *texLetterColonData);
}

/// Initiates miner texture data.
void GameWidget::GLes2InitTextureMiner()
{
    if (texMinerData != NULL)
        qFatal("GameWidget::GLes2InitTextureMiner() : exists already!");

    texMinerData = new TextureData;
    texMinerData->name = TextureIndexMiner;
    texMinerData->width = 12.0f;
    texMinerData->height = 12.0f;

    QImage img(":/images/miner_64x64.png");
    GLes2InitTexture(img, *texMinerData);
}

/// Initiates rewind R letter texture data.
void GameWidget::GLes2InitTextureRewindR()
{
    if (texRewindRData != NULL)
        qFatal("GameWidget::GLes2InitTextureRewindR() : exists already!");

    texRewindRData = new TextureData;
    texRewindRData->name = TextureIndexRewindR;
    texRewindRData->width = 0.15f * engine->gameViewRangeX;
    texRewindRData->height = 0.20f * engine->gameViewRangeY;

    QImage img(":/images/64x64_r.png");
    GLes2InitTexture(img, *texRewindRData);
}

/// Initiates time text texture data.
void GameWidget::GLes2InitTextureTime()
{
    if (texTimeData != NULL)
        qFatal("GameWidget::GLes2InitTextureTime() : exists already!");

    texTimeData = new TextureData;
    texTimeData->name = TextureIndexTime;
    texTimeData->width = 32.0f;
    texTimeData->height = 8.0f;

    QImage img(":/images/128x32_Time.png");
    GLes2InitTexture(img, *texTimeData);
}

/// Initiates get ready text texture data.
void GameWidget::GLes2InitTextureGetReady()
{
    if (texGetReadyData != NULL)
        qFatal("GameWidget::GLes2InitTextureGetReady() : exists already!");

    texGetReadyData = new TextureData;
    texGetReadyData->name = TextureIndexGetReady;
    texGetReadyData->width = FRAME_BIG_TEXTURE_SIZE * 4.0f;
    texGetReadyData->height = FRAME_BIG_TEXTURE_SIZE;

    QImage img(":/images/256x32_GetReady.png");
    GLes2InitTexture(img, *texGetReadyData);
}

/// Initiates level text texture data.
void GameWidget::GLes2InitTextureLevel()
{
    if (texLevelData != NULL)
        qFatal("GameWidget::GLes2InitTextureLevel() : exists already!");

    texLevelData = new TextureData;
    texLevelData->name = TextureIndexLevel;
    texLevelData->width = FRAME_BIG_TEXTURE_SIZE * 2.0f;
    texLevelData->height = FRAME_BIG_TEXTURE_SIZE;

    QImage img(":/images/128x32_Level.png");
    GLes2InitTexture(img, *texLevelData);
}

/// Initiates level text small texture data.
void GameWidget::GLes2InitTextureLevelSmall()
{
    if (texLevelSmallData != NULL)
        qFatal("GameWidget::GLes2InitTextureLevelSmall() : exists already!");

    texLevelSmallData = new TextureData;
    texLevelSmallData->name = TextureIndexLevelSmall;
    texLevelSmallData->width = 32.0f;
    texLevelSmallData->height = 8.0f;

    QImage img(":/images/128x32_Level.png");
    GLes2InitTexture(img, *texLevelSmallData);
}

/// Initiates game over text texture data.
void GameWidget::GLes2InitTextureGameOver()
{
    if (texGameOverData != NULL)
        qFatal("GameWidget::GLes2InitTextureGameOver() : exists already!");

    texGameOverData = new TextureData;
    texGameOverData->name = TextureIndexGameOver;
    texGameOverData->width = FRAME_BIG_TEXTURE_SIZE * 4.0f;
    texGameOverData->height = FRAME_BIG_TEXTURE_SIZE;

    QImage img(":/images/256x32_GameOver.png");
    GLes2InitTexture(img, *texGameOverData);
}

/// Initiates paused text texture data.
void GameWidget::GLes2InitTexturePaused()
{
    if (texPausedData != NULL) 
        qFatal("GameWidget::GLes2InitTexturePaused() : exists already!");

    texPausedData = new TextureData;
    texPausedData->name = TextureIndexPaused;
    texPausedData->width = FRAME_BIG_TEXTURE_SIZE * 4.0f;
    texPausedData->height = FRAME_BIG_TEXTURE_SIZE * 2.0f;

    QImage img(":/images/256x128_Paused.png");
    GLes2InitTexture(img, *texPausedData);
}

/// Initiates pause button texture data.
void GameWidget::GLes2InitTextureButtonPause()
{
    if (texButtonPauseData != NULL)
        qFatal("GameWidget::GLes2InitTextureButtonPause() : exists already!");

    texButtonPauseData = new TextureData;
    texButtonPauseData->name = TextureIndexButtonPause;
    texButtonPauseData->width = 16.0f;
    texButtonPauseData->height = 16.0f;

    QImage img(":/images/64x64_pause.png");
    GLes2InitTexture(img, *texButtonPauseData);
}

/// Initiates play button texture data.
void GameWidget::GLes2InitTextureButtonPlay()
{
    if (texButtonPlayData != NULL)
        qFatal("GameWidget::GLes2InitTextureButtonPlay() : exists already!");

    texButtonPlayData = new TextureData;
    texButtonPlayData->name = TextureIndexButtonPlay;
    texButtonPlayData->width = 16.0f;
    texButtonPlayData->height = 16.0f;

    QImage img(":/images/64x64_play.png");
    GLes2InitTexture(img, *texButtonPlayData);
}

/// Initiates ball mode button texture data.
void GameWidget::GLes2InitTextureButtonModeBall()
{
    if (texButtonModeBallData != NULL)
        qFatal("GameWidget::GLes2InitTextureButtonModeBall() : exists already!");

    texButtonModeBallData = new TextureData;
    texButtonModeBallData->name = TextureIndexButtonModeBall;
    texButtonModeBallData->width = 20.0f;
    texButtonModeBallData->height = 20.0f;

    QImage img(":/images/64x64_modeBall.png");
    GLes2InitTexture(img, *texButtonModeBallData);
}

/// Initiates cone mode button texture data.
void GameWidget::GLes2InitTextureButtonModeCone()
{
    if (texButtonModeConeData != NULL)
        qFatal("GameWidget::GLes2InitTextureButtonModeCone() : exists already!");

    texButtonModeConeData = new TextureData;
    texButtonModeConeData->name = TextureIndexButtonModeCone;
    texButtonModeConeData->width = 20.0f;
    texButtonModeConeData->height = 20.0f;

    QImage img(":/images/64x64_modeCone.png");
    GLes2InitTexture(img, *texButtonModeConeData);
}

/// Initiates bomb button texture data.
void GameWidget::GLes2InitTextureButtonBomb()
{
    if (texButtonBombData != NULL)
        qFatal("GameWidget::GLes2InitTextureButtonBomb() : exists already!");

    texButtonBombData = new TextureData;
    texButtonBombData->name = TextureIndexButtonBomb;
    texButtonBombData->width = 20.0f;
    texButtonBombData->height = 20.0f;

    QImage img(":/images/64x64_bbutton.png");
    GLes2InitTexture(img, *texButtonBombData);
}

/// Initiates comet enemy texture data.
void GameWidget::GLes2InitCometTexture()
{
    if (cometData != NULL)
        qFatal("GameWidget::GLes2InitTextureComet() : exists already!");

    cometData = new CometTextureData;
    cometData->name = TextureIndexComet;

    QImage img(":/images/128x512_comet.png");
    QImage conv = QGLWidget::convertToGLFormat(img);

    glBindTexture(GL_TEXTURE_2D, textureNames[cometData->name]);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, conv.width(), conv.height(),
                 0, GL_RGBA, GL_UNSIGNED_BYTE, conv.bits());

    // write the texture coordinate data for triangles.
    // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    //    A-----B    1st triangle = A-B-C
    //    |  x  |    2nd triangle = C-D-A
    //    D--E--C
    //    |     |    3rd triangle = E-F-G
    //   |       |   4th triangle = E-H-F
    //   I-G-F-H-J   5th triangle = E-G-D
    //               6th triangle = E-H-C
    //               7th triangle = D-G-I
    //               8th triangle = C-J-H

    const GLfloat radius = GameEngine::GameObjectRadiusComet;

    const GLfloat length = 80.0f;   // see GLes2ScaleCometTexture().
    const GLfloat width = 80.0f;    // see GLes2ScaleCometTexture().

    const GLfloat z = -25.0f;

    cometData->vertexData.resize(0);

    cometData->vertexData.push_back(-radius);               // A
    cometData->vertexData.push_back(+radius);
    cometData->vertexData.push_back(z);

    cometData->vertexData.push_back(+radius);               // B
    cometData->vertexData.push_back(+radius);
    cometData->vertexData.push_back(z);

    cometData->vertexData.push_back(+radius);               // C
    cometData->vertexData.push_back(-radius);
    cometData->vertexData.push_back(z);

    cometData->vertexData.push_back(+radius);               // C
    cometData->vertexData.push_back(-radius);
    cometData->vertexData.push_back(z);

    cometData->vertexData.push_back(-radius);               // D
    cometData->vertexData.push_back(-radius);
    cometData->vertexData.push_back(z);

    cometData->vertexData.push_back(-radius);               // A
    cometData->vertexData.push_back(+radius);
    cometData->vertexData.push_back(z);

    const GLfloat stretch = 0.70f;
    const GLfloat radiusCD = radius * stretch;

    cometData->vertexData.push_back(0.0f);                  // E
    cometData->vertexData.push_back(-radius);
    cometData->vertexData.push_back(z);

    cometData->vertexData.push_back(0.0f);                  // F
    cometData->vertexData.push_back(-(radius + length));    // index = 22
    cometData->vertexData.push_back(z);

    cometData->vertexData.push_back(-width * 0.5f);         // G ; i = 24
    cometData->vertexData.push_back(-(radius + length));    // index = 25
    cometData->vertexData.push_back(z);

    cometData->vertexData.push_back(0.0f);                  // E
    cometData->vertexData.push_back(-radius);
    cometData->vertexData.push_back(z);

    cometData->vertexData.push_back(+width * 0.5f);         // H ; i = 30
    cometData->vertexData.push_back(-(radius + length));    // index = 31
    cometData->vertexData.push_back(z);

    cometData->vertexData.push_back(0.0f);                  // F
    cometData->vertexData.push_back(-(radius + length));    // index = 34
    cometData->vertexData.push_back(z);

    cometData->vertexData.push_back(0.0f);                  // E
    cometData->vertexData.push_back(-radius);
    cometData->vertexData.push_back(z);

    cometData->vertexData.push_back(-width * 0.5f);         // G ; i = 39
    cometData->vertexData.push_back(-(radius + length));    // index = 40
    cometData->vertexData.push_back(z);

    cometData->vertexData.push_back(-radiusCD);             // D
    cometData->vertexData.push_back(-radius);
    cometData->vertexData.push_back(z);

    cometData->vertexData.push_back(0.0f);                  // E
    cometData->vertexData.push_back(-radius);
    cometData->vertexData.push_back(z);

    cometData->vertexData.push_back(+width * 0.5f);         // H ; i = 48
    cometData->vertexData.push_back(-(radius + length));    // index = 49
    cometData->vertexData.push_back(z);

    cometData->vertexData.push_back(+radiusCD);             // C
    cometData->vertexData.push_back(-radius);
    cometData->vertexData.push_back(z);

    cometData->vertexData.push_back(-radiusCD);             // D
    cometData->vertexData.push_back(-radius);
    cometData->vertexData.push_back(z);

    cometData->vertexData.push_back(-width * 0.5f);         // G ; i = 57
    cometData->vertexData.push_back(-(radius + length));    // index = 58
    cometData->vertexData.push_back(z);

    cometData->vertexData.push_back(-width);                // I ; i = 60
    cometData->vertexData.push_back(-(radius + length));    // index = 61
    cometData->vertexData.push_back(z);

    cometData->vertexData.push_back(+radiusCD);             // C
    cometData->vertexData.push_back(-radius);
    cometData->vertexData.push_back(z);

    cometData->vertexData.push_back(+width);                // J ; i = 66
    cometData->vertexData.push_back(-(radius + length));    // index = 67
    cometData->vertexData.push_back(z);

    cometData->vertexData.push_back(+width * 0.5f);         // H ; i = 69
    cometData->vertexData.push_back(-(radius + length));    // index = 70
    cometData->vertexData.push_back(z);

    cometData->textureCoordinateData.resize(0);

    const GLfloat x_left = 0.00f;
    const GLfloat x_right = 1.00f;

    const GLfloat y_up = 1.00f;
    const GLfloat y_mid = 0.85f;
    const GLfloat y_down = 0.00f;

    cometData->textureCoordinateData.push_back(x_left);              // A
    cometData->textureCoordinateData.push_back(y_up);

    cometData->textureCoordinateData.push_back(x_right);             // B
    cometData->textureCoordinateData.push_back(y_up);

    cometData->textureCoordinateData.push_back(x_right);             // C
    cometData->textureCoordinateData.push_back(y_mid);

    cometData->textureCoordinateData.push_back(x_right);             // C
    cometData->textureCoordinateData.push_back(y_mid);

    cometData->textureCoordinateData.push_back(x_left);              // D
    cometData->textureCoordinateData.push_back(y_mid);

    cometData->textureCoordinateData.push_back(x_left);              // A
    cometData->textureCoordinateData.push_back(y_up);

    const GLfloat x_leftFull = 0.50f * (1.0f - stretch);
    const GLfloat x_rightFull = 1.0f - x_leftFull;

    const GLfloat x_leftHalf = 0.50f * (1.0f - stretch * 0.50f);
    const GLfloat x_rightHalf = 1.0f - x_leftHalf;

    cometData->textureCoordinateData.push_back(0.50f);               // E
    cometData->textureCoordinateData.push_back(y_mid);

    cometData->textureCoordinateData.push_back(0.50f);               // F
    cometData->textureCoordinateData.push_back(y_down);

    cometData->textureCoordinateData.push_back(x_leftHalf);          // G
    cometData->textureCoordinateData.push_back(y_down);

    cometData->textureCoordinateData.push_back(0.50f);               // E
    cometData->textureCoordinateData.push_back(y_mid);

    cometData->textureCoordinateData.push_back(x_rightHalf);         // H
    cometData->textureCoordinateData.push_back(y_down);

    cometData->textureCoordinateData.push_back(0.50f);               // F
    cometData->textureCoordinateData.push_back(y_down);

    cometData->textureCoordinateData.push_back(0.50f);               // E
    cometData->textureCoordinateData.push_back(y_mid);

    cometData->textureCoordinateData.push_back(x_leftHalf);          // G
    cometData->textureCoordinateData.push_back(y_down);

    cometData->textureCoordinateData.push_back(x_leftFull);          // D
    cometData->textureCoordinateData.push_back(y_mid);

    cometData->textureCoordinateData.push_back(0.50f);               // E
    cometData->textureCoordinateData.push_back(y_mid);

    cometData->textureCoordinateData.push_back(x_rightHalf);         // H
    cometData->textureCoordinateData.push_back(y_down);

    cometData->textureCoordinateData.push_back(x_rightFull);         // C
    cometData->textureCoordinateData.push_back(y_mid);

    cometData->textureCoordinateData.push_back(x_leftFull);          // D
    cometData->textureCoordinateData.push_back(y_mid);

    cometData->textureCoordinateData.push_back(x_leftHalf);          // G
    cometData->textureCoordinateData.push_back(y_down);

    cometData->textureCoordinateData.push_back(x_leftFull);          // I
    cometData->textureCoordinateData.push_back(y_down);

    cometData->textureCoordinateData.push_back(x_rightFull);         // C
    cometData->textureCoordinateData.push_back(y_mid);

    cometData->textureCoordinateData.push_back(x_rightFull);         // J
    cometData->textureCoordinateData.push_back(y_down);

    cometData->textureCoordinateData.push_back(x_rightHalf);         // H
    cometData->textureCoordinateData.push_back(y_down);

    cometData->numberOfVertices = 6 + 18;
}

/// Scales the comet enemy texture to new \a length and \a width.
void GameWidget::GLes2ScaleCometTexture(GLfloat length, GLfloat width)
{
    const GLfloat radius = GameEngine::GameObjectRadiusComet;

    cometData->vertexData[22] = -(radius + length);

    cometData->vertexData[24] = -width * 0.5f;
    cometData->vertexData[25] = -(radius + length);

    cometData->vertexData[30] = +width * 0.5f;
    cometData->vertexData[31] = -(radius + length);

    cometData->vertexData[34] = -(radius + length);

    cometData->vertexData[39] = -width * 0.5f;
    cometData->vertexData[40] = -(radius + length);

    cometData->vertexData[48] = +width * 0.5f;
    cometData->vertexData[49] = -(radius + length);

    cometData->vertexData[57] = -width * 0.5f;
    cometData->vertexData[58] = -(radius + length);

    cometData->vertexData[60] = -width;
    cometData->vertexData[61] = -(radius + length);

    cometData->vertexData[66] = +width;
    cometData->vertexData[67] = -(radius + length);

    cometData->vertexData[69] = +width * 0.5f;
    cometData->vertexData[70] = -(radius + length);
}

/*!
 *   Draws the OpenGL ES 2 game screen.
 *   Method handles painting of background, game objects,
 *   on screen control buttons and game information.
 *
 *   To paint more enemies add call to paint method in the enemyList loop, e.g.
 *   \code
 *   for (int i = 0;i < engine->enemyList.size();i++) {
 *       GameObject *go = engine->enemyList[i];
 *       ...
 *       if (go->objectType() == TypeMissile) {
 *           Missile *m = static_cast<Missile *>(go);
 *           GLes2PaintMissile(pos[0], pos[1], m);
 *       }
 *       ...
 *   }
 *   \endcode
 *
 * \param timestep  A timestep calculated for current game loop (current game frame length) 
 *                  in milliseconds.
 */
void GameWidget::GLes2Paint(GLfloat timestep)
{
    paintWithBlackHole = (engine->blackhole != NULL && engine->blackhole->isAlive());

#ifdef SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS
#ifdef SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE

    // make sure that the hyperJumpInData and hyperJumpOutData
    // have not been mixed up between the normal game mode and
    // the rewind mode.

    if (TimerManager::hyperJumpInData != NULL && TimerManager::hyperJumpInData->isRewind() != rewindMode) {
        qFatal("GameWidget::GLes2Paint() : bad TimerManager::hyperJumpInData.");
    }

    if (TimerManager::hyperJumpOutData != NULL && TimerManager::hyperJumpOutData->isRewind() != rewindMode) {
        qFatal("GameWidget::GLes2Paint() : bad TimerManager::hyperJumpOutData.");
    }

#endif  // SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE
#endif  // SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

    glDisable(GL_DEPTH_TEST);

    GLes2PaintBackGround();

    // paint the game objects.
    //^^^^^^^^^^^^^^^^^^^^^^^^^
    // if normal game mode -> paint using current location.
    // if rewind mode -> paint using a stored location.

    bool visible;
    GLfloat pos[2];

    // paint the spacecraft (also mirrored if needed).

    visible = isObjectVisible(engine->miner);

    if (engine->timerManager->isActive(GameTimer::stateGetReadyTimer))
        visible = false;

    if (TimerManager::hyperJumpOutData != NULL && TimerManager::hyperJumpOutData->isHidden())
        visible = false;

    if (TimerManager::hyperJumpInData != NULL && TimerManager::hyperJumpInData->isHidden())
        visible = false;

    if (visible) {
        getObjectPosition(engine->miner, pos);
        GLes2PaintSpaceShip(pos[0], pos[1]);

#ifdef SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE
        if (rewindMode)
            engine->miner->checkWrappingRewind(pos, engine->gameViewRangeX, engine->gameViewRangeY);
#endif  // SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE

        const bool wrapX = (engine->miner->wrappingFlags() & WRAPPING_FLAG_WRAP_X);
        if (wrapX)
            GLes2PaintSpaceShip(engine->miner->wrappedPosX(), pos[1]);

        const bool wrapY = (engine->miner->wrappingFlags() & WRAPPING_FLAG_WRAP_Y);
        if (wrapY)
            GLes2PaintSpaceShip(pos[0], engine->miner->wrappedPosY());

        if (wrapX && wrapY)
            GLes2PaintSpaceShip(engine->miner->wrappedPosX(), engine->miner->wrappedPosY());
    }

    glEnable(GL_DEPTH_TEST);

    // start painting objects which benefit from GL_CULL_FACE.

    glEnable(GL_CULL_FACE);

    // paint the asteroids (also mirrored if needed).

    for (int i = 0;i < engine->asteroidList.count();i++) {
        Asteroid *a = engine->asteroidList[i];

        visible = isObjectVisible(a);
        if (visible) {
            getObjectPosition(a, pos);
            GLes2PaintAsteroid(pos[0], pos[1], a);

#ifdef SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE
            if (rewindMode)
                a->checkWrappingRewind(pos, engine->gameViewRangeX, engine->gameViewRangeY);
#endif  // SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE

            const bool wrapX = (a->wrappingFlags() & WRAPPING_FLAG_WRAP_X);
            if (wrapX)
                GLes2PaintAsteroid(a->wrappedPosX(), pos[1], a);

            const bool wrapY = (a->wrappingFlags() & WRAPPING_FLAG_WRAP_Y);
            if (wrapY)
                GLes2PaintAsteroid(pos[0], a->wrappedPosY(), a);

            if (wrapX && wrapY)
                GLes2PaintAsteroid(a->wrappedPosX(), a->wrappedPosY(), a);
        }
    }

    for (int i = 0; i < engine->collectibleList.count(); i++) {
        Collectible *c = engine->collectibleList[i];

        visible = isObjectVisible(c);

        if (visible) {
            const bool skipCF = (c->objectType() == TypeCollectibleBomb ||
                                 c->objectType() == TypeCollectibleHyperJump);

            if (skipCF)
                glDisable(GL_CULL_FACE);

            getObjectPosition(c, pos);
            GLes2PaintCollectible(pos[0], pos[1], c);

#ifdef SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE
            if (rewindMode)
                c->checkWrappingRewind(pos, engine->gameViewRangeX, engine->gameViewRangeY);
#endif  // SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE

            const bool wrapX = (c->wrappingFlags() & WRAPPING_FLAG_WRAP_X);
            if (wrapX)
                GLes2PaintCollectible(c->wrappedPosX(), pos[1], c);

            const bool wrapY = (c->wrappingFlags() & WRAPPING_FLAG_WRAP_Y);
            if (wrapY)
                GLes2PaintCollectible(pos[0], c->wrappedPosY(), c);

            if (wrapX && wrapY)
                GLes2PaintCollectible(c->wrappedPosX(), c->wrappedPosY(), c);

            if (skipCF)
                glEnable(GL_CULL_FACE);
        }
    }

    for (int i = 0;i < engine->enemyList.size();i++) {
        GameObject *go = engine->enemyList[i];

        visible = isObjectVisible(go);

        if (visible) {
            getObjectPosition(go, pos);

            if (go->objectType() == TypeUfo) {
                Ufo *u = static_cast<Ufo *>(go);
                GLes2PaintUfo(pos[0], pos[1], u);
            }

            if (go->objectType() == TypeMissile) {
                Missile *m = static_cast<Missile *>(go);
                GLes2PaintMissile(pos[0], pos[1], m);
            }

            if (go->objectType() == TypeMeteorShower) {
                MeteorShower *ms = static_cast<MeteorShower *>(go);

                GLfloat currentTime;
#ifdef SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE
                if (rewindMode)
                    currentTime = (*rewindCurrentFrameInfo)->timeStamp();
                else
                    currentTime = engine->totalTimeCounter;
#else   // SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE
                currentTime = engine->totalTimeCounter;
#endif  // SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE

                GLes2PaintMeteorShower(pos[0], pos[1], ms, currentTime);
            }
        }
    }

    // do next painting operations...
    // <pistä seuraavat piirtokomennot tähän>

    // finish painting objects which benefit from GL_CULL_FACE.

    glDisable(GL_CULL_FACE);

    // paint the particles.

    GLes2PaintParticles(timestep);

    // paint the control gauges if needed.

#ifdef SMASHMINER_CONFIG_ENABLE_CONTROL_GAUGES
#ifndef SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE
    GLes2PaintControlGauges();
#else   // SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE
    if (rewindMode)
        GLes2PaintRewindGauge();
    else
        GLes2PaintControlGauges();
#endif  // SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE
#endif  // SMASHMINER_CONFIG_ENABLE_CONTROL_GAUGES

    // paint the hyperjump debug data if requested.

#ifdef SMASHMINER_CONFIG_ENABLE_HYPERJUMP_DESTINATION_VISUALIZATION
   if (engine->hyperJumpNavigator) 
       GLes2PaintHJDestinations();
#endif

   // paint the textures.

   if (engine->comet != NULL) {
       visible = isObjectVisible((GameObject *) engine->comet);
       if (visible) {
           getObjectPosition((GameObject *) engine->comet, pos);

           GLfloat currentTime;
#ifdef SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE
           if (rewindMode)
               currentTime = (*rewindCurrentFrameInfo)->timeStamp();
           else
               currentTime = engine->totalTimeCounter;
#else   // SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE
           currentTime = engine->totalTimeCounter;
#endif  // SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE

           GLes2PaintComet(pos[0], pos[1], engine->comet, currentTime);
       }
   }

#ifdef SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE

    if (rewindMode) {
        GLfloat startTime = engine->frameInfoQ.head()->timeStamp();
        GLfloat currentTime = (*rewindCurrentFrameInfo)->timeStamp();
        int blink = 2.0f * (currentTime - startTime);

        if (blink % 2 == 0)
            GLes2PaintTextureRewindR();
    }

#endif // SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE

     GLes2PaintScore();
     GLes2PaintLevel();
     GLes2PaintTime();

    GLes2PaintMinersCountStyle1(engine->minersLeft);
    //GLes2PaintMinersCountStyle2(engine->minersLeft);

    GLes2PaintHyperJumpsCount(engine->hyperJumpCount);

    GLes2PaintBombsCount(engine->bombCount);

    if (isPaused) {
        GLes2PaintTextureButtonPlay();
        GLes2PaintTexturePaused();
    } else {
        GLes2PaintTextureButtonPause();

        if (engine->timerManager->isActive(GameTimer::stateGetReadyTimer))
            GLes2PaintTextureGetReady();

        if (engine->timerManager->isActive(GameTimer::stateLevelChangingTimer))
            GLes2PaintTextureNewLevel();

        if (engine->timerManager->isActive(GameTimer::stateGameOverTimer))
            GLes2PaintTextureGameOver();
    }

    SpaceCraftMode m;
    if (engine->timerManager->time(GameTimer::shipBall2ConeTimer) >= 0)
        m = SpaceCraftConeMode;
    else if (engine->timerManager->time(GameTimer::shipCone2BallTimer) >= 0)
        m = SpaceCraftBallMode;
    else if (engine->miner->mode() == SpaceCraftBallMode)
        m = SpaceCraftBallMode;
    else
        m = SpaceCraftConeMode;

    if (m != SpaceCraftBallMode)
        GLes2PaintTextureButtonModeBall();
    else
        GLes2PaintTextureButtonModeCone();

    // ready...
}

#ifdef SMASHMINER_CONFIG_ENABLE_HYPERJUMP_DESTINATION_VISUALIZATION
/// Draws estimations of possible hyperjump destinations
void GameWidget::GLes2PaintHJDestinations()
{
    HyperJumpDestination *hjd;

    hjd = engine->hyperJumpNavigator->getAtIndex(0);
    GLes2PaintTexture(texHyperJumpData, hjd->posX, hjd->posY, 0.0f);

    hjd = engine->hyperJumpNavigator->getAtIndex(1);
    GLes2PaintTexture(texHyperJumpData, hjd->posX, hjd->posY, 0.0f);

    hjd = engine->hyperJumpNavigator->getAtIndex(2);
    GLes2PaintTexture(texHyperJumpData, hjd->posX, hjd->posY, 0.0f);
}

#endif  // SMASHMINER_CONFIG_ENABLE_HYPERJUMP_DESTINATION_VISUALIZATION

/*! Draws background using \c GameWidget::programBackground or \c
    GameWidget::programBackgroundEffect2D shader program if black hole of bomb effect is active.
 */
void GameWidget::GLes2PaintBackGround()
{
    // Two optional ways to draw background. Currently option 1 is used.
    //^^^^^^^^^
    // option 1 : paint the entire screen area using a texture AND skip glClear(GL_COLOR_BUFFER_BIT)
    // option 2 : do first glClear(GL_COLOR_BUFFER_BIT) with black and then paint some background 
    //            textures.
    spBackGround *p;

    if (!paintWithBlackHole && TimerManager::bombEffectData == NULL) {
        p = programBackground;
        p->bind();
    }
    else {
        // Set the black hole data
        QVector3D bh_data;
        if (paintWithBlackHole) {
            GLfloat bhpos[2];
            getObjectPosition(engine->blackhole, bhpos);
            bh_data.setX(bhpos[0]);
            bh_data.setY(bhpos[1]);
            bh_data.setZ(engine->blackhole->radrad);
        } else {
            bh_data.setZ(-1000.0f);
        }

        // Set the bomb data
        QVector4D bomb_data;
        if (TimerManager::bombEffectData != NULL) {
            const GLfloat t = engine->timerManager->timeRel(GameTimer::bombEffectTimer);
            // the game view width is from -100...+100 -> make big enough.
            TimerManager::bombEffectData->setShockwaveRadius(-20.0f + t * 350.0f);
            TimerManager::bombEffectData->setLightwaveRadius(-40.0f + t * 300.0f);
            bomb_data.setX(TimerManager::bombEffectData->posX());
            bomb_data.setY(TimerManager::bombEffectData->posY());
            bomb_data.setZ(TimerManager::bombEffectData->shockwaveRadius());
            bomb_data.setW(TimerManager::bombEffectData->lightwaveRadius());
        } else {
            bomb_data.setZ(-1000.0f);
            bomb_data.setW(-1000.0f);
        }

        p = programBackgroundEffect2D;
        p->bind();
        programBackgroundEffect2D->setBlackHoleData3(bh_data);
        programBackgroundEffect2D->setBombData(bomb_data);
    }

    p->setMatrix(pmvMatrix);

    glBindTexture(GL_TEXTURE_2D, textureNames[TextureIndexBG]);

    p->setVertexArray(&backgroundData2x[0]);
    p->setTextureArray(&backgroundData2x[backgroundTextureCoordinateDataStartIndex]);

    p->enableVertexArray();
    p->enableTextureArray();

    glDrawArrays(GL_TRIANGLES, 0, backgroundVertexDataCount / 3);

    p->disableVertexArray();
    p->disableTextureArray();

    glBindTexture(GL_TEXTURE_2D, 0);

    p->release();
}

/*! Draws spaceship to position given at \a positionX and \a positionY using
 * \c GameWidget::programConstantMaterial or \c GameWidget::programConstanMaterialEffect3D
 * if black hole is active or space ship is in hyper jump in or out state.
 * 
 * Hyper jump impact to space ship is also done using black hole effect. 
 * The black hole data calculation is then based on the hyper jump in/out timer progress.
 */
void GameWidget::GLes2PaintSpaceShip(GLfloat positionX, GLfloat positionY)
{
    QMatrix4x4 tmpM(pmvMatrix);
    tmpM.translate(positionX, positionY);

    const bool doHyperJumpOut = (TimerManager::hyperJumpOutData != NULL 
        && TimerManager::hyperJumpOutData->isTransforming());

    const bool doHyperJumpIn = (TimerManager::hyperJumpInData != NULL 
        && TimerManager::hyperJumpInData->isTransforming());

    spConstantMaterial *constantMaterialProgram;

    if (doHyperJumpOut || doHyperJumpIn || paintWithBlackHole) {
        QVector4D blackholeData;

        if (doHyperJumpOut) {
            // Draw the black hole effect according to hyper jump out timer progress
            const int maxTime = engine->timerManager->timerLength(GameTimer::hyperJumpOutTimer) / 2;
            const int time = engine->timerManager->time(GameTimer::hyperJumpOutTimer) - maxTime / 2;
            const GLfloat relTime = (GLfloat) time / (GLfloat) maxTime;
            const GLfloat rad = sin(relTime * (M_PI / 2.0f)) * 15.0f;

            blackholeData.setX(TimerManager::hyperJumpOutData->posX() - positionX);
            blackholeData.setY(TimerManager::hyperJumpOutData->posY() - positionY);
            blackholeData.setZ(0.0);
            blackholeData.setW(rad * rad);
        }
        else if (doHyperJumpIn) {
            // Draw the black hole effect according to hyper jump in timer progress
            const int maxTime = engine->timerManager->timerLength(GameTimer::hyperJumpInTimer) / 2;
            const int time = engine->timerManager->time(GameTimer::hyperJumpInTimer) - maxTime / 2;
            const GLfloat relTime = 1.0f - (GLfloat) time / (GLfloat) maxTime;
            const GLfloat rad = sin(relTime * (M_PI / 2.0f)) * 15.0f;

            blackholeData.setX(TimerManager::hyperJumpInData->posX() - positionX);
            blackholeData.setY(TimerManager::hyperJumpInData->posY() - positionY);
            blackholeData.setZ(0.0);
            blackholeData.setW(rad * rad);
        }
        else if (paintWithBlackHole) {
            GLfloat blackholePosition[2];
            getObjectPosition(engine->blackhole, blackholePosition);

            blackholeData.setX(blackholePosition[0] - positionX);
            blackholeData.setY(blackholePosition[1] - positionY);
            blackholeData.setZ(0.0);
            blackholeData.setW(engine->blackhole->radrad);
        }
        else {
            qFatal("GameWidget::GLes2PaintSpaceShip() : bad effect request.");
        }

        constantMaterialProgram = programConstanMaterialEffect3D;
        constantMaterialProgram->bind();
        programConstanMaterialEffect3D->setBlackHoleData4(blackholeData);
    }
    else {
        constantMaterialProgram = programConstantMaterial;
        constantMaterialProgram->bind();
    }

    constantMaterialProgram->setMatrix(tmpM);
    constantMaterialProgram->setLightDirection(lightPosition);

    constantMaterialProgram->enableVertexArray();
    constantMaterialProgram->enableNormalArray();

    constantMaterialProgram->setVertexArray(spaceshipConeVertexData);
    constantMaterialProgram->setNormalArray(spaceshipConeNormalData);
    constantMaterialProgram->setLightMaterialAmbient(spaceshipConeAmbientLmData);
    constantMaterialProgram->setLightMaterialDiffuse(spaceshipConeDiffuseLmData);
    glDrawArrays(GL_TRIANGLES, 0, SPACESHIP_CONE_INDICES / 3);

    constantMaterialProgram->setVertexArray(spaceshipBallVertexData);
    constantMaterialProgram->setNormalArray(spaceshipBallNormalData);
    constantMaterialProgram->setLightMaterialAmbient(spaceshipBallAmbientLmData);
    constantMaterialProgram->setLightMaterialDiffuse(spaceshipBallDiffuseLmData);
    glDrawArrays(GL_TRIANGLES, 0, SPACESHIP_BALL_INDICES / 3);

    constantMaterialProgram->disableVertexArray();
    constantMaterialProgram->disableNormalArray();

    constantMaterialProgram->release();
}

/*! Draws \a asteroid to position given at \a positionX and \a positionY.
 * Asteroid can be large asteroid or one of the medium sized asteroids set in  
 * \c GameObject::type using one of the \c ::GameObjectType enumeration values:
 * - GameObjectType::TypeAsteroidLarge
 * - GameObjectType::TypeAsteroidMediumA
 * - GameObjectType::TypeAsteroidMediumB
 * - GameObjectType::TypeAsteroidMediumC
 *
 * Vertex, normal and colour data is set according to asteroid
 * type.
 *
 * Asteroid is painted using \c GameWidget::programVariableMaterial or 
 * \c GameWidget::programVariableMaterial3D if black hole is active.
 */
void GameWidget::GLes2PaintAsteroid(GLfloat positionX, GLfloat positionY, Asteroid * asteroid)
{
    GLfloat *vertexData = vertexDataByType(asteroid);
    GLfloat *normalData = normalDataByType(asteroid);
    GLfloat *colourData = colourDataByType(asteroid);

    int indCount;
    indexCountByType(asteroid, indCount);

    QMatrix4x4 tmpM(pmvMatrix);
    tmpM.translate(positionX, positionY, asteroid->posZ());

    // apply the asteroid rotation to the matrix.

    tmpM.rotate(asteroid->rotationAngle(), asteroid->rotationAxis(0), asteroid->rotationAxis(1), 
                asteroid->rotationAxis(2));

    // avoid transforming each normal by transforming the light position instead.

    QMatrix4x4 mapperM;
    mapperM.rotate(-asteroid->rotationAngle(), asteroid->rotationAxis(0), asteroid->rotationAxis(1),
                   asteroid->rotationAxis(2));

    const QVector4D light4global(lightPosition.x(), lightPosition.y(), lightPosition.z(), 1.0f);
    QVector4D light4local = mapperM.map(light4global);

    QVector3D light3(light4local.x(), light4local.y(), light4local.z());

    spVariableMaterial *variableMaterialProgram;

    if (!paintWithBlackHole) {
        variableMaterialProgram = programVariableMaterial;
        variableMaterialProgram->bind();
    } else {
        // avoid transforming each vertex by transforming the blackhole position instead.
        // define a vector from UFO to blackhole and transform it using the mapper matrix.

        GLfloat blackholePosition[2];
        getObjectPosition(engine->blackhole, blackholePosition);
        const QVector4D bh_glob(blackholePosition[0] - positionX, blackholePosition[1] - positionY, 
                                -asteroid->posZ(), 1.0f);

        QVector4D bh_loc = mapperM.map(bh_glob);
        bh_loc.setW(engine->blackhole->radrad);

        variableMaterialProgram = programVariableMaterial3D;
        variableMaterialProgram->bind();
        programVariableMaterial3D->setBlackHoleData4(bh_loc);
    }

    variableMaterialProgram->setMatrix(tmpM);

    variableMaterialProgram->setLightMaterialAmbient(lightAmbient);
    variableMaterialProgram->setLightMaterialDiffuse(lightDiffuse);
    variableMaterialProgram->setLightDirection(light3);

    variableMaterialProgram->enableVertexArray();
    variableMaterialProgram->enableNormalArray();
    variableMaterialProgram->enableMaterialArray();

    variableMaterialProgram->setVertexArray(vertexData);
    variableMaterialProgram->setNormalArray(normalData);
    variableMaterialProgram->setMaterialArray(colourData);
    glDrawArrays(GL_TRIANGLES, 0, indCount / 3);

    variableMaterialProgram->disableVertexArray();
    variableMaterialProgram->disableNormalArray();
    variableMaterialProgram->disableMaterialArray();

    variableMaterialProgram->release();
}

/*! Draws \a collectible to position given at \a positionX and \a positionY.
 * The collectible type is defined with \c GameObject::type.
 * The following collectible types are currently supported:
 * - GameObjectType::TypeCollectibleBomb
 * - GameObjectType::TypeCollectibleHyperJump
 * - GameObjectType::TypeCollectibleMineralA
 * - GameObjectType::TypeCollectibleMineralB
 * - GameObjectType::TypeCollectibleMineralC
 * - GameObjectType::TypeCollectibleMineralD
 */
void GameWidget::GLes2PaintCollectible(GLfloat positionX, GLfloat positionY, Collectible *collectible)
{
    switch(collectible->objectType()) {
        case TypeCollectibleBomb:
            // this would look better as a single triangle?
            GLes2PaintTextureBlackHole(texBombData, positionX, positionY, 0.0f);
            break;
        case TypeCollectibleHyperJump:
            // this is not used at the moment...
            GLes2PaintTextureBlackHole(texHyperJumpData, positionX, positionY, 0.0f);
            break;
        case TypeCollectibleMineralA:   // fall through
        case TypeCollectibleMineralB:   // fall through
        case TypeCollectibleMineralD:   // fall through
        case TypeCollectibleMineralC:
            GLes2PaintRotatingCollectible(positionX, positionY, 
                    static_cast<RotatingCollectible *>(collectible));
            break;
        default:
            qFatal("Type %d not supported collectible type.", collectible->objectType());
    }
}

/*! Draws rotating \a collectible (instance of \c RotatingCollectible) 
 * to position given at \a positionX and \a positionY.
 *
 * The vertex and normal data are set according to \c GameObject::type.
 *
 * The rotating collectible is painted using \c GameWidget::programConstantMaterialSpecular
 * or \c GameWidget::programConstantMaterialSpecularEffect3D if black hole is active.
 */
void GameWidget::GLes2PaintRotatingCollectible(GLfloat positionX, GLfloat positionY, 
    RotatingCollectible *collectible)
{
    GLfloat *vertexData = vertexDataByType(collectible);
    GLfloat *normalData = normalDataByType(collectible);

    QVector4D ambientLmData; 
    ambientLmDataByType(collectible, ambientLmData);

    QVector4D diffuseLmData; 
    diffuseLmDataByType(collectible, diffuseLmData);

    int indCount;
    indexCountByType(collectible, indCount);

    QMatrix4x4 tmpM(pmvMatrix);
    tmpM.translate(positionX, positionY);

    // apply the collectible's rotation to the matrix
    tmpM.rotate(collectible->rotationAngle(), collectible->rotationAxis(0), 
                collectible->rotationAxis(1), collectible->rotationAxis(2));

    // create an identity matrix and rotate according to object rotation
    QMatrix4x4 mapperM;
    mapperM.rotate(-collectible->rotationAngle(), collectible->rotationAxis(0), 
                   collectible->rotationAxis(1), collectible->rotationAxis(2));

    // light location
    const QVector4D light4glob(lightPosition.x(), lightPosition.y(), lightPosition.z(), 1.0f);
    
    // light location using mapper matrix
    QVector4D light4loc = mapperM.map(light4glob);

    QVector3D light3(light4loc.x(), light4loc.y(), light4loc.z());

    spConstantMaterial *p;

    if (!paintWithBlackHole) {
        p = programConstantMaterialSpecular;
        p->bind();
    } else {
        GLfloat bhpos[2];   // Black hole position
        getObjectPosition(engine->blackhole, bhpos);

        const QVector4D bh_glob(bhpos[0] - positionX, bhpos[1] - positionY, 0.0, 1.0f);

        QVector4D bh_loc = mapperM.map(bh_glob);
        bh_loc.setW(engine->blackhole->radrad);

        p = programConstantMaterialSpecularEffect3D;
        p->bind();
        programConstantMaterialSpecularEffect3D->setBlackHoleData4(bh_loc);
    }

    p->setMatrix(tmpM);
    p->setLightDirection(light3);

    p->enableVertexArray();
    p->enableNormalArray();

    p->setVertexArray(vertexData);
    p->setNormalArray(normalData);
    p->setLightMaterialAmbient(ambientLmData);
    p->setLightMaterialDiffuse(diffuseLmData);
    glDrawArrays(GL_TRIANGLES, 0, indCount / 3);

    p->disableVertexArray();
    p->disableNormalArray();

    p->release();
}

/*! Draws an \c Ufo instance to position given at \a positionX and \a positionY using
 * \c GameWidget::programConstantMaterial or \c  GameWidget::programConstanMaterialEffect3D
 * if black hole is active.
 */
void GameWidget::GLes2PaintUfo(GLfloat positionX, GLfloat positionY, Ufo *ufo)
{
    const GLfloat posZ = 5.0f;

    QMatrix4x4 tmpM(pmvMatrix);
    tmpM.translate(positionX, positionY, posZ);

    QMatrix4x4 ufolightM(tmpM); // store the matrix for UFO light at this stage and apply light rotation.
    ufolightM.rotate(-ufo->rotationAngle(), ufo->rotationAxis(0), ufo->rotationAxis(1), 
                     ufo->rotationAxis(2));

    // apply the UFO rotation to the matrix.

    tmpM.rotate(ufo->rotationAngle(), ufo->rotationAxis(0), ufo->rotationAxis(1), 
                ufo->rotationAxis(2));

    // avoid transforming each normal by transforming the light position instead.

    QMatrix4x4 mapperM;
    mapperM.rotate(-ufo->rotationAngle(), ufo->rotationAxis(0), ufo->rotationAxis(1), 
                   ufo->rotationAxis(2));

    const QVector4D light4glob(lightPosition.x(), lightPosition.y(), lightPosition.z(), 1.0f);
    QVector4D light4loc = mapperM.map(light4glob);

    QVector3D light3_a(light4loc.x(), light4loc.y(), light4loc.z());

    // prepare to invert the mappings for the UFO light which rotates on opposite direction.

    QMatrix4x4 inv_mapperM;
    inv_mapperM.rotate(ufo->rotationAngle(), ufo->rotationAxis(0), ufo->rotationAxis(1), 
                       ufo->rotationAxis(2));

    spConstantMaterial *p;
    spConstantMaterialEff3D *pEff = NULL;

    QVector4D bh_glob;

    if (!paintWithBlackHole) {
        p = programConstantMaterial;
        p->bind();
    } else {
        // avoid transforming each vertex by transforming the blackhole position instead.
        // define a vector from UFO to blackhole and transform it using the mapper matrix.

        GLfloat bhpos[2];
        getObjectPosition(engine->blackhole, bhpos);
        bh_glob = QVector4D(bhpos[0] - positionX, bhpos[1] - positionY, -posZ, 1.0f);

        QVector4D bh_loc_a = mapperM.map(bh_glob);
        bh_loc_a.setW(engine->blackhole->radrad);

        p = programConstanMaterialEffect3D;
        p->bind();
        pEff = static_cast<spConstantMaterialEff3D *>(p);
        pEff->setBlackHoleData4(bh_loc_a);
    }

    p->setMatrix(tmpM);
    p->setLightDirection(light3_a);

    p->enableVertexArray();
    p->enableNormalArray();

    p->setVertexArray(ufoColor1VertexData);
    p->setNormalArray(ufoColor1NormalData);
    p->setLightMaterialAmbient(ufoColor1AmbientLmData);
    p->setLightMaterialDiffuse(ufoColor1DiffuseLmData);
    glDrawArrays(GL_TRIANGLES, 0, UFO_COLOR1_INDICES / 3);

    p->setVertexArray(ufoColor2VertexData);
    p->setNormalArray(ufoColor2NormalData);
    p->setLightMaterialAmbient(ufoColor2AmbientLmData);
    p->setLightMaterialDiffuse(ufoColor2DiffuseLmData);
    glDrawArrays(GL_TRIANGLES, 0, UFO_COLOR2_INDICES / 3);

    light4loc = inv_mapperM.map(light4glob);
    QVector3D light3_b(light4loc.x(), light4loc.y(), light4loc.z());

    p->setMatrix(ufolightM);            // change the matrix...
    p->setLightDirection(light3_b);     // update the light...

    if (paintWithBlackHole) {
        QVector4D bh_loc_b = inv_mapperM.map(bh_glob);
        bh_loc_b.setW(engine->blackhole->radrad);
        pEff->setBlackHoleData4(bh_loc_b);      // update the bh...
    }

    p->setVertexArray(ufoLightsVertexData);
    p->setNormalArray(ufoLightsNormalData);
    p->setLightMaterialAmbient(ufoLightsAmbientLmData);
    p->setLightMaterialDiffuse(ufoLightsDiffuseLmData);
    glDrawArrays(GL_TRIANGLES, 0, UFO_LIGHTS_INDICES / 3);

    p->disableVertexArray();
    p->disableNormalArray();
    p->release();
}

/*! Paints a \c Missile instance to position given at \a positionX and \a positionY.
 *
 * Projection matrix \c GameWidget::pmvMatrix (defined in \c GameWidget::GLes2SetupViewPort()) 
 * is used to create a transformation matrix \c tmpM to transform 
 * the missile location to screen viewport location.
 *
 * \code
 * QMatrix4x4 tmpM(pmvMatrix);
 * tmpM.translate(positionX, positionY, posZ);
 * \endcode
 *
 * Rotation is applied according to missile direction (rotation angle) using the missiles rotation
 * axis:
 * \code
 * tmpM.rotate(missile->rotationAngle(), missile->rotationAxis(0), missile->rotationAxis(1),
 *             missile->rotationAxis(2));
 * \endcode
 *
 * To achieve a correct light effect for the rotated object transforming each normal is avoided 
 * by transforming the light position instead. The missile rotation angle is used in the 
 * opposite direction to create a transformation matrix \c mapperM. The \c global light
 * direction vector (\c GameWidget::lightPosition as defined in \c GameWidget::GLes2Initialize()) 
 * is trasformed using \c mapperM.
 * \code
 * QMatrix4x4 mapperM;
 * mapperM.rotate(-missile->rotationAngle(), missile->rotationAxis(0), missile->rotationAxis(1),
 *                missile->rotationAxis(2));
 * const QVector4D light4glob(lightPosition.x(), lightPosition.y(), lightPosition.z(), 1.0f);
 * QVector4D light4loc = mapperM.map(light4glob);
 * QVector3D light3(light4loc.x(), light4loc.y(), light4loc.z());
 * \endcode
 *
 * The shader program for constant material (\c GameWidget::programConstantMaterial
 * or \c GameWidget::programConstanMaterialEffect3D if black hole effect is requested) is used.
 *
 * If black hole effect is requested, black hole location data is set to shader program:
 * \code
 * GLfloat bhpos[2];
 * getObjectPosition(engine->blackhole, bhpos);
 * QVector4D bh_glob(bhpos[0] - positionX, bhpos[1] - positionY, -posZ, 1.0f);
 * QVector4D bh_loc = mapperM.map(bh_glob);
 * bh_loc.setW(engine->blackhole->radrad);
 * programConstanMaterialEffect3D->setBlackHoleData4(bh_loc);
 * \endcode
 *
 * Set the transformation matrix and light direction vector to shader program:
 * \code
 * p->setMatrix(tmpM);
 * p->setLightDirection(light3);
 * \endcode
 *
 * Enable the vertex and normal arrays and set the vertex and normal data:
 * \code
 * p->enableVertexArray();
 * p->enableNormalArray();
 * p->setVertexArray(missileVertexData);
 * p->setNormalArray(missileNormalData);
 * \endcode
 *
 * Set the light effect vector data:
 * \code
 * p->setLightMaterialAmbient(missileLightsAmbientLmData);
 * p->setLightMaterialDiffuse(missileLightsDiffuseLmData);
 * \endcode
 *
 * Paint data and release resources:
 * \code
 * glDrawArrays(GL_TRIANGLES, 0, MISSILE_INDICES / 3);
 * p->disableVertexArray();
 * p->disableNormalArray();
 * p->release();
 * \endcode
 *
 * \param positionX Target X-coordinate
 * \param positionY Target Y-coordinate
 * \param missile Missile being painted
 */
void GameWidget::GLes2PaintMissile(GLfloat positionX, GLfloat positionY, Missile *missile)
{
    const GLfloat posZ = 5.0f;

    QMatrix4x4 tmpM(pmvMatrix);
    tmpM.translate(positionX, positionY, posZ);

#ifdef SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE
    // If in rewind mode, calculate the rotation
    if (rewindMode)
        missile->adjustRewindDirection(rewindCurrentFrame);
#endif  // SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE

    tmpM.rotate(missile->rotationAngle(), missile->rotationAxis(0), missile->rotationAxis(1), 
                missile->rotationAxis(2));

    // avoid transforming each normal by transforming the light position instead.

    QMatrix4x4 mapperM;
    mapperM.rotate(-missile->rotationAngle(), missile->rotationAxis(0), missile->rotationAxis(1), 
                   missile->rotationAxis(2));

    const QVector4D light4glob(lightPosition.x(), lightPosition.y(), lightPosition.z(), 1.0f);
    QVector4D light4loc = mapperM.map(light4glob);

    QVector3D light3(light4loc.x(), light4loc.y(), light4loc.z());

    spConstantMaterial *p;

    if (!paintWithBlackHole) {
        p = programConstantMaterial;
        p->bind();
    } else {
        GLfloat bhpos[2];
        getObjectPosition(engine->blackhole, bhpos);
        const QVector4D bh_glob(bhpos[0] - positionX, bhpos[1] - positionY, -posZ, 1.0f);

        QVector4D bh_loc = mapperM.map(bh_glob);
        bh_loc.setW(engine->blackhole->radrad);

        p = programConstanMaterialEffect3D;
        p->bind();
        programConstanMaterialEffect3D->setBlackHoleData4(bh_loc);
    }

    p->setMatrix(tmpM);
    p->setLightDirection(light3);

    p->enableVertexArray();
    p->enableNormalArray();

    p->setVertexArray(missileVertexData);
    p->setNormalArray(missileNormalData);
    p->setLightMaterialAmbient(missileLightsAmbientLmData);
    p->setLightMaterialDiffuse(missileLightsDiffuseLmData);
    glDrawArrays(GL_TRIANGLES, 0, MISSILE_INDICES / 3);

    p->disableVertexArray();
    p->disableNormalArray();
    p->release();
}

/*!
    Draws a \c Comet instance to position given at \a positionX and \a positionY.
    Attribute \a currentTime is needed to get length and width of comet at the time being rendered.

    Comet is rendered using \c GameWidget::programTexture shader program.
*/
void GameWidget::GLes2PaintComet(GLfloat positionX, GLfloat positionY, Comet *comet, 
                                 GLfloat currentTime)
{
    const GLfloat length = comet->getLength(currentTime);
    const GLfloat width = comet->getWidth(currentTime);

    GLes2ScaleCometTexture(length, width);

    const GLfloat positionZ = +20.0f;

    QMatrix4x4 tmpM(pmvMatrix);
    tmpM.translate(positionX, positionY, positionZ);

    GLfloat angle = 180.0f * comet->rotationAngle() / M_PI;
    tmpM.rotate(angle, comet->rotationAxis(0), comet->rotationAxis(1), comet->rotationAxis(2));

    QMatrix4x4 mapperM;
    mapperM.rotate(-angle, comet->rotationAxis(0), comet->rotationAxis(1), comet->rotationAxis(2));

    spTexture *p;

    // the BlackHole effect does not really work here.
    // -> so let's paint always without it!

    p = programTexture;
    p->bind();

    p->bind();

    glDepthMask(false);
    glEnable(GL_BLEND);

    glBindTexture(GL_TEXTURE_2D, textureNames[cometData->name]);

    p->setMatrix(tmpM);

    p->setVertexArray(&cometData->vertexData[0]);
    p->setTextureArray(&cometData->textureCoordinateData[0]);

    p->enableVertexArray();
    p->enableTextureArray();

    glDrawArrays(GL_TRIANGLES, 0, cometData->numberOfVertices);

    p->disableVertexArray();
    p->disableTextureArray();

    glBindTexture(GL_TEXTURE_2D, 0);

    glDisable(GL_BLEND);
    glDepthMask(true);

    p->release();
}

/*! Draws a \c MeteorShower instance to a position given at \a positionX and \a positionY.
 * The \a time parameter is used to calculate the meteor shower animation state at the given
 * time. Meteor shower is rendered using \c GameWidget::programVariableMaterial or
 * \c GameWidget::programVariableMaterial3D is black hole is active.
 */
void GameWidget::GLes2PaintMeteorShower(GLfloat positionX, GLfloat positionY, 
                                        MeteorShower *meteorShower, GLfloat time)
{
    meteorShower->animate(time);

    const GLfloat positionZ = +10.0f;

    GLfloat *vertexData = meteorShower->vertexArray();
    GLfloat *colourData = meteorShower->colorArray();

    const int numVertices = meteorShower->numVertices();

    QMatrix4x4 tmpM(pmvMatrix);
    tmpM.translate(positionX, positionY, positionZ);

    QVector3D light3(lightPosition.x(), lightPosition.y(), lightPosition.z());

    spVariableMaterial * p;

    if (!paintWithBlackHole) {
        p = programVariableMaterial;
        p->bind();
    } else {
        GLfloat bhpos[2];
        getObjectPosition(engine->blackhole, bhpos);
        QVector4D bh_glob(bhpos[0] - positionX, bhpos[1] - positionY, -positionZ, engine->blackhole->radrad);

        p = programVariableMaterial3D;
        p->bind();
        programVariableMaterial3D->setBlackHoleData4(bh_glob);
    }

    p->setMatrix(tmpM);

    p->setLightMaterialAmbient(lightAmbient);
    p->setLightMaterialDiffuse(lightDiffuse);
    p->setLightDirection(light3);

    // NOTE : we don't use normal data for MeteorShowers.
    //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

    p->enableVertexArray();
    p->enableMaterialArray();

    p->setVertexArray(vertexData);
    p->setMaterialArray(colourData);

    glDrawArrays(GL_TRIANGLES, 0, numVertices);

    p->disableVertexArray();
    p->disableMaterialArray();

    p->release();
}

/*! Draws OpenGL particles for current \a timestep.
 * 
 * If the rewind mode is active, gets the read index and time from 
 * \c FrameInfo object pointed currently by the \c GameWidget::rewindCurrentFrameInfo
 * interator.
 *
 * Otherwise the read index is taken from \c GameEngine::particle and the time
 * is current time.
 *
 * Read index is used for reading the particle data from particle buffer.
 * Particle buffers are stored in \c GameEngine::particle.
 *
 * Particles are painted using \c GameWidget::programParticles or 
 * \c GameWidget::programParticlesEffect2D if black hole is active.
 *
 */
void GameWidget::GLes2PaintParticles(GLfloat timestep)
{
    int readIndex;
    GLfloat currentTime;

    const GLfloat twoPi = 2.0f * M_PI;

    static GLfloat hyperJumpAngle = 0.0f;
    hyperJumpAngle += timestep * 0.010f;
    if (hyperJumpAngle > twoPi)     // wrap at 2pi.
        hyperJumpAngle -= twoPi;

#ifdef SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE
    if (rewindMode) {
        readIndex = (*rewindCurrentFrameInfo)->readIndexR();

        if (readIndex < 0)
            return;     // nothing to paint...

        currentTime = (*rewindCurrentFrameInfo)->timeStamp();
    }
    else {
        readIndex = engine->particleData().particleWriteIndex - 1;
        if (readIndex < 0)
            readIndex = SMASHMINER_CONFIG_PARTICLE_BUFFER_CAPACITY - 1;

        currentTime = engine->totalTimeCounter;
    }
#else
    readIndex = engine->particleData().particleWriteIndex - 1;
    if (readIndex < 0)
        readIndex = SMASHMINER_CONFIG_PARTICLE_BUFFER_CAPACITY - 1;


    currentTime = engine->totalTimeCounter;
#endif

    const GLfloat timeLimit = currentTime - SMASHMINER_CONFIG_PARTICLE_LIFETIME;
    if (engine->particleData().particleBuffer[readIndex].startTime < timeLimit)
        return;     // nothing to paint...

    int array1First = -1;
    int array1Last = readIndex;

    int array2First = -1;
    int array2Last = -1;

    int validIndex = -1;

    // the while-loop below will cycle infinitely if ALL particles
    // in the particleBuffer have their ages below particle lifetime.
    // this may happen is the particleBuffer size is severely
    // underestimated (compared to particle lifetime).

    int infiniteLoopBreaker = 0;

    const GLfloat offScreen = engine->gameViewRangeX + engine->gameViewRangeY;

    GLfloat bhRad = 0.0f;
    GLfloat bhPos[2] = { offScreen, offScreen };
    if (paintWithBlackHole) {
        bhRad = engine->blackhole->objectRadius();
        getObjectPosition(engine->blackhole, bhPos);
    }

    while (engine->particleData().particleBuffer[readIndex].startTime > timeLimit) {

        validIndex = readIndex;     // paint the particle from readIndex.

        const GLfloat ageAbs = currentTime - engine->particleData().particleBuffer[readIndex].startTime;
        const GLfloat ageRel = ageAbs / SMASHMINER_CONFIG_PARTICLE_LIFETIME;

        const GLfloat alpha = 1.0f - ageRel * ageRel;

        engine->particleData().pBufferB[readIndex].alpha = alpha;

        GLfloat tmpX;
        GLfloat tmpY;

        if (engine->particleData().particleBuffer[readIndex].particleType == ParticleTypeBlackHole) {

            // handle a blackhole particle.
            //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
            // multiply tmpDist by alpha so that particles seem to "fall in".

            GLfloat tmpAngle = engine->particleData().pBufferB[readIndex].friction;

            const GLfloat tmpDist = engine->particleData().particleBuffer[readIndex].startPosX * alpha;
            const GLfloat tmpSin = sin(tmpAngle) * tmpDist;
            const GLfloat tmpCos = cos(tmpAngle) * tmpDist;

            engine->particleData().pBufferA[readIndex].positionX = tmpSin * bhParticlesXAxis[0] 
                + tmpCos * bhParticlesYAxis[0] + bhPos[0];
            engine->particleData().pBufferA[readIndex].positionY = tmpSin * bhParticlesXAxis[1] 
                + tmpCos * bhParticlesYAxis[1] + bhPos[1];
            engine->particleData().pBufferA[readIndex].positionZ = tmpSin * bhParticlesXAxis[2] 
                + tmpCos * bhParticlesYAxis[2];

            // update the particle radial motion.

            const GLfloat angleChange 
                = engine->particleData().particleBuffer[readIndex].startPosY * timestep;

            tmpAngle += angleChange;
            if (tmpAngle > twoPi)   // wrap at 2pi.
                tmpAngle -= twoPi;

            engine->particleData().pBufferB[readIndex].friction = tmpAngle;
        }
        else if (engine->particleData().particleBuffer[readIndex].particleType == ParticleTypeHyperJump) {

            // handle a HyperJump particle.
            //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
            // use a stored position and mode to compute the effect.

            const int mode = (engine->particleData().pBufferB[readIndex].friction < 0.50f ? 0 : 1);

            GLfloat tmpRelT;
            if (!mode) tmpRelT = engine->timerManager->timeRel(GameTimer::hyperJumpOutTimer);
            else tmpRelT = engine->timerManager->timeRel(GameTimer::hyperJumpInTimer);

            const GLfloat shape = sin(tmpRelT * M_PI);

            const GLfloat tmpAngle = hyperJumpAngle + engine->particleData().particleBuffer[readIndex].startFrict;
            const GLfloat tmpDist = shape * shape * 10.0f;
            const GLfloat tmpSin = sin(tmpAngle) * tmpDist;
            const GLfloat tmpCos = cos(tmpAngle) * tmpDist;

            const GLfloat tmpPosX = engine->particleData().particleBuffer[readIndex].startPosX;
            const GLfloat tmpPosY = engine->particleData().particleBuffer[readIndex].startPosY;

            engine->particleData().pBufferA[readIndex].positionX = tmpSin * bhParticlesXAxis[0] 
                + tmpCos * bhParticlesYAxis[0] + tmpPosX;
            engine->particleData().pBufferA[readIndex].positionY = tmpSin * bhParticlesXAxis[1] 
                + tmpCos * bhParticlesYAxis[1] + tmpPosY;
            engine->particleData().pBufferA[readIndex].positionZ = 0.0;
        } else {

            // handle a normal particle.
            //^^^^^^^^^^^^^^^^^^^^^^^^^^^

            // update the particle position.

            tmpX = engine->particleData().particleBuffer[readIndex].velocityX * timestep;
            tmpY = engine->particleData().particleBuffer[readIndex].velocityY * timestep;

            engine->particleData().pBufferA[readIndex].positionX += tmpX;
            engine->particleData().pBufferA[readIndex].positionY += tmpY;

            // test if the particle dropped into a blackhole.
            // if it did, then stop it completely and move it into the
            // center of the blackhole -> it seems to disappear...

            if (paintWithBlackHole) {
                GLfloat distX = engine->particleData().pBufferA[readIndex].positionX - bhPos[0];
                GLfloat distY = engine->particleData().pBufferA[readIndex].positionY - bhPos[1];
                if (fabs(distX) < bhRad && fabs(distY) < bhRad) {
                    engine->particleData().pBufferA[readIndex].positionX = bhPos[0];
                    engine->particleData().pBufferA[readIndex].positionY = bhPos[1];
                    engine->particleData().particleBuffer[readIndex].velocityX = 0.0;
                    engine->particleData().particleBuffer[readIndex].velocityY = 0.0;
                    engine->particleData().pBufferB[readIndex].friction = 0.0;
                }
            }

            // update the particle velocity.

            GLfloat f = engine->particleData().pBufferB[readIndex].friction * timestep;

            tmpX = engine->particleData().particleBuffer[readIndex].startVelX * f;
            tmpY = engine->particleData().particleBuffer[readIndex].startVelY * f;

            engine->particleData().particleBuffer[readIndex].velocityX -= tmpX;
            engine->particleData().particleBuffer[readIndex].velocityY -= tmpY;
        }

        // paint the particle.

        if (infiniteLoopBreaker++ >= SMASHMINER_CONFIG_PARTICLE_BUFFER_CAPACITY) {
#ifdef SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

            static int suppressWarning = 0;
            if (suppressWarning++ % 1000 == 0)
                qWarning() << "GameWidget::GLes2PaintParticles() : PARTICLE_BUFFER overflow detected!";

#endif  // SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

            array1First = 0;
            array1Last = SMASHMINER_CONFIG_PARTICLE_BUFFER_CAPACITY - 1;

            array2First = -1;
            array2Last = -1;

            validIndex = -1;
            break;
        }

        if (--readIndex < 0) {
            readIndex = SMASHMINER_CONFIG_PARTICLE_BUFFER_CAPACITY - 1;

            // the array index is wrapped and we must
            // paint the particles as two separate arrays.

            array1First = 0;
            array2Last = readIndex;

            validIndex = -1;
        }
    }

    if (validIndex >= 0) {
        if (array2Last < 0) 
            array1First = validIndex; // we have just one array to paint.
        else
            array2First = validIndex; // we have two arrays to paint.
    }
    else {

        // if validIndex now has a value -1, we just wrapped readIndex
        // but no more valid particles were found from the buffer.
        // -> so we have just one array to paint then...

        array2Last = -1;
    }

    if (array1First < 0 || array1Last < array1First) {
#ifdef SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE
        qDebug() << "rewindMode= " << rewindMode;
#endif  // SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE
        qDebug() << "array1First= " << array1First << " array1Last= " << array1Last;
        qDebug() << "array2First= " << array2First << " array2Last= " << array2Last;
        qDebug() << "GameWidget::GLes2PaintParticles() : bad array1.";
#ifdef SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS
        exit(EXIT_FAILURE);
#else   // SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS
        return; // an error condition - skip painting!
#endif  // SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS
    }

    if (array2Last >= 0 && (array2First < 0 || array2Last < array2First)) {
#ifdef SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE
        qDebug() << "rewindMode= " << rewindMode;
#endif  // SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE
        qDebug() << "array1First= " << array1First << " array1Last= " << array1Last;
        qDebug() << "array2First= " << array2First << " array2Last= " << array2Last;
        qDebug() << "GameWidget::GLes2PaintParticles() : bad array2.";
#ifdef SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS
        exit(EXIT_FAILURE);
#else   // SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS
        return; // an error condition - skip painting!
#endif  // SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS
    }

    // now paint the particle array(s).
    //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

#ifdef SMASHMINER_CONFIG_ENABLE_PARTICLE_BLEND
    glDepthMask(false);
    glEnable(GL_BLEND);
#endif  // SMASHMINER_CONFIG_ENABLE_PARTICLE_BLEND

    GLfloat * arrayA = (GLfloat *) & engine->particleData().pBufferA[0].positionX;     // positionX is the first field of struct ParticleDataA.
    GLfloat * arrayB = (GLfloat *) & engine->particleData().pBufferB[0].size;          // size is the first field of struct ParticleDataB.
    int numVertices;

    spParticles * p;

    if (!paintWithBlackHole) {
        p = programParticles;
        p->bind();
    } else {
        GLfloat bhpos[2];
        getObjectPosition(engine->blackhole, bhpos);

        QVector3D bh_data;
        bh_data.setX(bhpos[0]);
        bh_data.setY(bhpos[1]);
        bh_data.setZ(engine->blackhole->radrad);

        p = programParticlesEffect2D;
        p->bind();
        programParticlesEffect2D->setBlackHoleData3(bh_data);
    }

    p->setMatrix(pmvMatrix);

    p->enableVertexArray();
    p->setVertexArray(arrayA);

    p->enablePDataArray();
    p->setPDataArray(arrayB);

    numVertices = array1Last - array1First + 1;
    if (numVertices > 0) {
        glDrawArrays(GL_POINTS, array1First, numVertices);

#ifdef SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE
        if (!rewindMode)
            engine->currentFrameStats.numParticles += numVertices;
#else   // SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE
        engine->currentFrameStats.numParticles += numVertices;
#endif  // SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE
    }

    if (array2Last >= 0) {
        numVertices = array2Last - array2First + 1;
        if (numVertices > 0) {
            glDrawArrays(GL_POINTS, array2First, numVertices);

#ifdef SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE
            if (!rewindMode)
                engine->currentFrameStats.numParticles += numVertices;
#else   // SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE
            engine->currentFrameStats.numParticles += numVertices;
#endif  // SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE
        }
    }

    p->disableVertexArray();
    p->disablePDataArray();

    p->release();

#ifdef SMASHMINER_CONFIG_ENABLE_PARTICLE_BLEND
    glDisable(GL_BLEND);
    glDepthMask(true);
#endif  // SMASHMINER_CONFIG_ENABLE_PARTICLE_BLEND
}

/*!
    Paints the given texture object.
    The \a positionX, \a positionY, \a positionZ values are for translating the object.
    The texture object must be a rectangle object consisting of two triangles.
 */
void GameWidget::GLes2PaintTexture(TextureData * textureData, GLfloat positionX, GLfloat positionY, GLfloat positionZ)
{
    QMatrix4x4 tmpM(pmvMatrix);
    tmpM.translate(positionX, positionY, positionZ);

    spTexture *textureProgram = programTexture;

    textureProgram->bind();

    glDepthMask(false);
    glEnable(GL_BLEND);

    glBindTexture(GL_TEXTURE_2D, textureNames[textureData->name]);

    textureProgram->setMatrix(tmpM);

    textureProgram->setVertexArray(&textureData->vertexData[0]);
    textureProgram->setTextureArray(&textureData->textureCoordinateData[0]);

    textureProgram->enableVertexArray();
    textureProgram->enableTextureArray();

    glDrawArrays(GL_TRIANGLES, 0, 6);

    textureProgram->disableVertexArray();
    textureProgram->disableTextureArray();

    glBindTexture(GL_TEXTURE_2D, 0);

    glDisable(GL_BLEND);
    glDepthMask(true);

    textureProgram->release();
}

/*!
    Paints a given texture with BlackHole effect.
    The \a positionX and \a positionY, \a positionZ values are for translating the object.
    The texture object must be a rectangle object consisting of two triangles.
 */
void GameWidget::GLes2PaintTextureBlackHole(TextureData * textureData, GLfloat positionX, GLfloat positionY, GLfloat positionZ)
{
    if (!paintWithBlackHole) {
        GLes2PaintTexture(textureData, positionX, positionY, positionZ);
    } else {
        QMatrix4x4 tmpM(pmvMatrix);
        tmpM.translate(positionX, positionY, positionZ);

        spTextureEff3D *p = programTextureEffects3D;

        GLfloat bhpos[2];
        getObjectPosition(engine->blackhole, bhpos);

        QVector4D bh_data(bhpos[0] - positionX, bhpos[1] - positionY, -positionZ, engine->blackhole->radrad);

        p->bind();

        glDepthMask(false);
        glEnable(GL_BLEND);

        glBindTexture(GL_TEXTURE_2D, textureNames[textureData->name]);

        p->setMatrix(tmpM);

        p->setBlackHoleData4(bh_data);

        p->setVertexArray(&textureData->vertexData[0]);
        p->setTextureArray(&textureData->textureCoordinateData[0]);

        p->enableVertexArray();
        p->enableTextureArray();

        glDrawArrays(GL_TRIANGLES, 0, 6);

        p->disableVertexArray();
        p->disableTextureArray();

        glBindTexture(GL_TEXTURE_2D, 0);

        glDisable(GL_BLEND);
        glDepthMask(true);

        p->release();
    }
}

/// Draws \a number texture to given \a x and \a y position.
void GameWidget::GLes2PaintTextureNumber(int number, GLfloat x, GLfloat y)
{
    switch (number) {
    case 0:
        GLes2PaintTexture(texNumber0Data, x, y, FRAME_TEXTURE_Z_POS);
        break;
    case 1:
        GLes2PaintTexture(texNumber1Data, x, y, FRAME_TEXTURE_Z_POS);
        break;
    case 2:
        GLes2PaintTexture(texNumber2Data, x, y, FRAME_TEXTURE_Z_POS);
        break;
    case 3:
        GLes2PaintTexture(texNumber3Data, x, y, FRAME_TEXTURE_Z_POS);
        break;
    case 4:
        GLes2PaintTexture(texNumber4Data, x, y, FRAME_TEXTURE_Z_POS);
        break;
    case 5:
        GLes2PaintTexture(texNumber5Data, x, y, FRAME_TEXTURE_Z_POS);
        break;
    case 6:
        GLes2PaintTexture(texNumber6Data, x, y, FRAME_TEXTURE_Z_POS);
        break;
    case 7:
        GLes2PaintTexture(texNumber7Data, x, y, FRAME_TEXTURE_Z_POS);
        break;
    case 8:
        GLes2PaintTexture(texNumber8Data, x, y, FRAME_TEXTURE_Z_POS);
        break;
    case 9:
        GLes2PaintTexture(texNumber9Data, x, y, FRAME_TEXTURE_Z_POS);
        break;
    default:
        break;
    }
}

/// Draws big \a number texture to given \a x and \a y position.
void GameWidget::GLes2PaintTextureBigNumber(int number, GLfloat x, GLfloat y)
{
    switch (number) {
    case 0:
        GLes2PaintTexture(texBigNumber0Data, x, y, FRAME_TEXTURE_Z_POS);
        break;
    case 1:
        GLes2PaintTexture(texBigNumber1Data, x, y, FRAME_TEXTURE_Z_POS);
        break;
    case 2:
        GLes2PaintTexture(texBigNumber2Data, x, y, FRAME_TEXTURE_Z_POS);
        break;
    case 3:
        GLes2PaintTexture(texBigNumber3Data, x, y, FRAME_TEXTURE_Z_POS);
        break;
    case 4:
        GLes2PaintTexture(texBigNumber4Data, x, y, FRAME_TEXTURE_Z_POS);
        break;
    case 5:
        GLes2PaintTexture(texBigNumber5Data, x, y, FRAME_TEXTURE_Z_POS);
        break;
    case 6:
        GLes2PaintTexture(texBigNumber6Data, x, y, FRAME_TEXTURE_Z_POS);
        break;
    case 7:
        GLes2PaintTexture(texBigNumber7Data, x, y, FRAME_TEXTURE_Z_POS);
        break;
    case 8:
        GLes2PaintTexture(texBigNumber8Data, x, y, FRAME_TEXTURE_Z_POS);
        break;
    case 9:
        GLes2PaintTexture(texBigNumber9Data, x, y, FRAME_TEXTURE_Z_POS);
        break;
    default:
        break;
    }
}

/// Draws letter X texture to given \a x and \a y positions.
void GameWidget::GLes2PaintTextureLetterX(GLfloat x, GLfloat y)
{
    GLes2PaintTexture(texLetterXData, x, y, FRAME_TEXTURE_Z_POS);
}

/// Draws letter colon texture to given \a x and \a y positions.
void GameWidget::GLes2PaintTextureLetterColon(GLfloat x, GLfloat y)
{
    GLes2PaintTexture(texLetterColonData, x, y, FRAME_TEXTURE_Z_POS);
}

/// Draws miner texture to given \a x and \a y positions.
void GameWidget::GLes2PaintTextureMiner(GLfloat x, GLfloat y)
{
    GLes2PaintTexture(texMinerData, x, y, FRAME_TEXTURE_Z_POS);
}

/// Draws game screen info miner \a count by number, X and one miner texture.
void GameWidget::GLes2PaintMinersCountStyle1(int count)
{
    // assume here that all number textures have the same width.
    const GLfloat numberWidth = texNumber0Data->width;

    const GLfloat minerWidth = texMinerData->width * 0.80f;

    GLfloat mCountPosX = engine->gameViewRangeX * +0.50f;
    GLfloat mCountPosY = engine->gameViewRangeY * +0.90f;
    if (count > 0) {
        if (count > 9)
            count = 9;
        GLes2PaintTextureNumber(count, mCountPosX - numberWidth, mCountPosY);
        GLes2PaintTextureLetterX(mCountPosX, mCountPosY);
        GLes2PaintTextureMiner(mCountPosX + minerWidth, mCountPosY);
    }
}

/// Draws game screen info \a count of miners by miner textures.
void GameWidget::GLes2PaintMinersCountStyle2(int count)
{
    GLfloat mCountPosX = engine->gameViewRangeX * +0.25f;
    GLfloat mCountPosY = engine->gameViewRangeY * +0.90f;

    mCountPosX += 0.5f * count * texMinerData->width;

    for (int i = 0;i < count;i++) {
        GLes2PaintTextureMiner(mCountPosX, mCountPosY);
        mCountPosX -= texMinerData->width;
    }
}

/// Draws hyper jump \a count game screen info.
void GameWidget::GLes2PaintHyperJumpsCount(int count)
{
    // assume here that all number textures have the same width.
    const GLfloat numberWidth = texNumber0Data->width;

    GLfloat hjCountPosX = engine->gameViewRangeX * -0.40f;
    GLfloat hjCountPosY = engine->gameViewRangeY - texHyperJumpData->height;
    if (count > 0) {
        if (count > 9)
            count = 9;
        GLes2PaintTextureNumber(count, hjCountPosX - numberWidth, hjCountPosY);
        GLes2PaintTextureLetterX(hjCountPosX, hjCountPosY);
        GLes2PaintTexture(texHyperJumpData, hjCountPosX + texLetterXData->width, hjCountPosY, FRAME_TEXTURE_Z_POS);
    }
}

/// Draws bomb \a count game screen info.
void GameWidget::GLes2PaintBombsCount(int count)
{
    // assume here that all number textures have the same height.
    const GLfloat numberHeight = texNumber0Data->height;

    const GLfloat bombButtonWidth = texButtonBombData->width * 0.75f;

    GLfloat bCountPosX = lefthanded ?
                -engine->gameViewRangeX + bombButtonWidth + texLetterXData->width + bombButtonWidth :
                engine->gameViewRangeX - bombButtonWidth;
    GLfloat bCountPosY = -(engine->gameViewRangeY - numberHeight * 2.5f);
    if (count > 0) {
        if (count > 9)
            count = 9;
        GLes2PaintTextureNumber(count, bCountPosX - texLetterXData->width - bombButtonWidth, bCountPosY);
        GLes2PaintTextureLetterX(bCountPosX - bombButtonWidth, bCountPosY);
        GLes2PaintTextureButtonBomb(bCountPosX, bCountPosY);
    }
}

/// Draws game "Score" game screen info.
void GameWidget::GLes2PaintScore()
{
    // assume here that all number textures have the same width and height.
    const GLfloat numberWidth = texNumber0Data->width;
    const GLfloat numberHeight = texNumber0Data->height;

    const GLfloat x = (engine->gameViewRangeX - (numberWidth * 6.0f));
    const GLfloat y = -(engine->gameViewRangeY - numberHeight);
    QList<int> list = engine->score();
    int num=0;
    for (int round=0; round<6; round++) {
        num = list.at(round);
        if(round==0)
            GLes2PaintTextureNumber(num, x, y);
        if(round==1)
            GLes2PaintTextureNumber(num, x + numberWidth, y);
        if(round==2)
            GLes2PaintTextureNumber(num, x + (numberWidth * 2), y);
        if(round==3)
            GLes2PaintTextureNumber(num, x + (numberWidth * 3), y);
        if(round==4)
            GLes2PaintTextureNumber(num, x + (numberWidth * 4), y);
        if(round==5)
            GLes2PaintTextureNumber(num, x + (numberWidth * 5), y);
    }
}

/// Draws current "Level" game screen info.
void GameWidget::GLes2PaintLevel()
{
    int level = engine->level->currentLevel();

    // assume here that all number textures have the same width and height.
    const GLfloat numberWidth = texNumber0Data->width;
    const GLfloat numberHeight = texNumber0Data->height;

    const GLfloat levelWidth = texLevelSmallData->width * 0.45f;
    const GLfloat levelStartPosition = numberWidth * 3.0f;

    GLfloat x = -(levelWidth/2);
    GLfloat y = -(engine->gameViewRangeY - numberHeight);

    GLes2PaintTexture(texLevelSmallData, x, y, FRAME_TEXTURE_Z_POS);

    int tens = level / 10;
    int ones = level % 10;


    // paint ones
    x += levelStartPosition;
    GLes2PaintTextureNumber(ones, x, y);

    // paint tens
    x -= numberWidth;
    GLes2PaintTextureNumber(tens, x, y);
}

/// Draws current "Time" game screen info.
void GameWidget::GLes2PaintTime()
{
    int currentTime;

#ifdef SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE

    if (rewindMode)
        currentTime = (int) (*rewindCurrentFrameInfo)->timeStamp();
    else
        currentTime = (int) engine->totalTimeCounter;

#else   // SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE

    currentTime = (int) engine->totalTimeCounter;

#endif  // SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE

    // assume here that all number textures have the same width and height.
    const GLfloat numberWidth = texNumber0Data->width;
    const GLfloat numberHeight = texNumber0Data->height;

    const GLfloat timeWidth = texTimeData->width * 0.45f;
    const GLfloat timeStartPosition = numberWidth * 6.0f;

    GLfloat x = -engine->gameViewRangeX + timeWidth;
    GLfloat y = -(engine->gameViewRangeY - numberHeight);

    GLes2PaintTexture(texTimeData, x, y, FRAME_TEXTURE_Z_POS);

    int minutes = currentTime / 60;
    int seconds = currentTime % 60;

    int number;

    // paint seconds.

    number = seconds % 10;
    x += timeStartPosition;
    GLes2PaintTextureNumber(number, x, y);

    number = seconds / 10;
    x -= numberWidth;
    GLes2PaintTextureNumber(number, x, y);

    // paint colon.

    x -= numberWidth;
    GLes2PaintTextureLetterColon(x, y);

    // paint minutes.

    number = minutes % 10;
    x -= numberWidth;
    GLes2PaintTextureNumber(number, x, y);

    number = minutes / 10;
    x -= numberWidth;
    GLes2PaintTextureNumber(number, x, y);
}

/// Draws rewind "R" game screen symbol.
void GameWidget::GLes2PaintTextureRewindR()
{
    const GLfloat x = -0.65 * engine->gameViewRangeX;
    const GLfloat y = +0.75 * engine->gameViewRangeY;
    GLes2PaintTexture(texRewindRData, x, y, FRAME_TEXTURE_Z_POS);
}

/// Draws "Get Ready" game screen text.
void GameWidget::GLes2PaintTextureGetReady()
{
    const int blankTime = 500;
    const int milliseconds = 3000;  // this must be full seconds!!!

    int timerLength = engine->timerManager->timerLength(GameTimer::stateGetReadyTimer);

#ifdef SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

    if (timerLength != blankTime * 2 + milliseconds)
        qFatal("GameWidget::GLes2PaintTextureGetReady() : bad timer count.");

#endif  // SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

    int skipTime = timerLength - blankTime * 2;
    int displayTime = engine->timerManager->time(GameTimer::stateGetReadyTimer) - blankTime;
    if (displayTime < 0 || displayTime >= skipTime)
        return;

    const GLfloat heightAdjust = texBigNumber0Data->height * 0.50f;
    GLes2PaintTexture(texGetReadyData, 0.0f, +heightAdjust, FRAME_TEXTURE_Z_POS);

    skipTime = skipTime - milliseconds;
    displayTime = milliseconds - (displayTime - skipTime / 2);
    const int displayStatus = displayTime % 1000;
    const int displayNumber = displayTime / 1000 + 1;

    if (displayStatus < 200 || displayStatus >= 800)
        return;

    GLes2PaintTextureBigNumber(displayNumber, 0.0f, -heightAdjust);
}

/// Draws "New Level" game screen text.
void GameWidget::GLes2PaintTextureNewLevel()
{
    const int blankTime = 500;

    int timerLength = engine->timerManager->timerLength(GameTimer::stateLevelChangingTimer);

#ifdef SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

    if (timerLength < blankTime * 2)
        qFatal("GameWidget::GLes2PaintTextureNewLevel() : bad timer count.");

#endif  // SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

    int skipTime = timerLength - blankTime * 2;
    int displayTime = engine->timerManager->time(GameTimer::stateLevelChangingTimer) - blankTime;
    if (displayTime < 0 || displayTime >= skipTime)
        return;

    const GLfloat heightAdjust = texBigNumber0Data->height * 0.50f;
    GLes2PaintTexture(texLevelData, 0.0f, +heightAdjust, FRAME_TEXTURE_Z_POS);

    const int level = engine->level->currentLevel() + 1;
    const GLfloat posX = texBigNumber0Data->width * 0.50f;
    int number;

    number = level % 10;
    GLes2PaintTextureBigNumber(number, +posX, -heightAdjust);

    number = level / 10;
    GLes2PaintTextureBigNumber(number, -posX, -heightAdjust);
}

/// Draws "Game Over" game screen text.
void GameWidget::GLes2PaintTextureGameOver()
{
    GLes2PaintTexture(texGameOverData, 0.0f, 0.0f, FRAME_TEXTURE_Z_POS);
}

void GameWidget::GLes2PaintTexturePaused()
{
    GLes2PaintTexture(texPausedData, 0.0f, 0.0f, FRAME_TEXTURE_Z_POS);
}

/// Draws "Pause" game screen button.
void GameWidget::GLes2PaintTextureButtonPause()
{
    const GLfloat x = lefthanded ? +0.88 * engine->gameViewRangeX : -0.88 * engine->gameViewRangeX;
    const GLfloat y = +0.84 * engine->gameViewRangeY;
    GLes2PaintTexture(texButtonPauseData, x, y, FRAME_TEXTURE_Z_POS);
}

/// Draws "Play" game screen button.
void GameWidget::GLes2PaintTextureButtonPlay()
{
    const GLfloat x = lefthanded ? +0.88 * engine->gameViewRangeX : -0.88 * engine->gameViewRangeX;
    const GLfloat y = +0.84 * engine->gameViewRangeY;
    GLes2PaintTexture(texButtonPlayData, x, y, FRAME_TEXTURE_Z_POS);
}

/// Draws "Ball mode" game screen button.
void GameWidget::GLes2PaintTextureButtonModeBall()
{
    const GLfloat x = lefthanded ? -0.88 * engine->gameViewRangeX : +0.88 * engine->gameViewRangeX;
    const GLfloat y = +0.84 * engine->gameViewRangeY;
    GLes2PaintTexture(texButtonModeBallData, x, y, FRAME_TEXTURE_Z_POS);
}

/// Draws "Cone mode" game screen button.
void GameWidget::GLes2PaintTextureButtonModeCone()
{
    const GLfloat x = lefthanded ? -0.88 * engine->gameViewRangeX : +0.88 * engine->gameViewRangeX;
    const GLfloat y = +0.84 * engine->gameViewRangeY;
    GLes2PaintTexture(texButtonModeConeData, x, y, FRAME_TEXTURE_Z_POS);
}

/// Draws "Bomb" game screen button.
void GameWidget::GLes2PaintTextureButtonBomb(GLfloat x, GLfloat y)
{
    GLes2PaintTexture(texButtonBombData, x, y, FRAME_TEXTURE_Z_POS);
}

#ifdef SMASHMINER_CONFIG_ENABLE_CONTROL_GAUGES
/// Draws control game screen gauges.
void GameWidget::GLes2PaintControlGauges()
{
    const GLfloat gauge_x_pos = 0.95 * engine->gameViewRangeX;
    const GLfloat gauge_y_pos = 0.95 * engine->gameViewRangeY;

    const GLfloat gauge_x_range = 0.90 * engine->gameViewRangeX;
    const GLfloat gauge_y_range = 0.90 * engine->gameViewRangeY;

    const GLfloat gauge_x_value = gauge_x_range * engine->steeringX();
    const GLfloat gauge_y_value = gauge_y_range * engine->steeringY();

    const GLfloat gauge_x_thickness = 0.01 * engine->gameViewRangeX;
    const GLfloat gauge_y_thickness = 0.01 * engine->gameViewRangeY;

    const GLfloat z = +75.0f;

    GLfloat gauge_vertices[] = {
        -gauge_x_range, -gauge_y_pos, z,    // x-gauge line
        +gauge_x_range, -gauge_y_pos, z,
        -gauge_x_pos, -gauge_y_range, z,    // y-gauge line
        -gauge_x_pos, +gauge_y_range, z,

        // express a quad as two triangles:
        // ABCD -> ABC + CDA

        0.0, -gauge_y_pos - gauge_x_thickness, z,   // x-gauge triangles
        0.0, -gauge_y_pos + gauge_x_thickness, z,
        gauge_x_value, -gauge_y_pos + gauge_x_thickness, z,
        gauge_x_value, -gauge_y_pos + gauge_x_thickness, z,
        gauge_x_value, -gauge_y_pos - gauge_x_thickness, z,
        0.0, -gauge_y_pos - gauge_x_thickness, z,
        -gauge_x_pos - gauge_y_thickness, 0.0, z,   // y-gauge triangles
        -gauge_x_pos + gauge_y_thickness, 0.0, z,
        -gauge_x_pos + gauge_y_thickness, gauge_y_value, z,
        -gauge_x_pos + gauge_y_thickness, gauge_y_value, z,
        -gauge_x_pos - gauge_y_thickness, gauge_y_value, z,
        -gauge_x_pos - gauge_y_thickness, 0.0, z
    };

    spControlGauges * p = programControlCauges;

    p->bind();

    p->setMatrix(pmvMatrix);

    p->enableVertexArray();
    p->setVertexArray(gauge_vertices);

    glDrawArrays(GL_LINES, 0, 4);
    glDrawArrays(GL_TRIANGLES, 4, 12);

    p->disableVertexArray();

    p->release();
}

#ifdef SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE
/// Draws rewind game screen gauge.
void GameWidget::GLes2PaintRewindGauge()
{
    const GLfloat gauge_y_pos = 0.95 * engine->gameViewRangeY;

    const GLfloat gauge_x_range = 0.90 * engine->gameViewRangeX;

    const GLfloat tmp1 = rewindCurrentFrame - rewindFirstFrame;
    const GLfloat tmp2 = rewindLastFrame - rewindFirstFrame;

    const GLfloat gauge_x_value = gauge_x_range * 2.0 * tmp1 / tmp2;

    const GLfloat gauge_x_thickness = 0.01 * engine->gameViewRangeX;

    const GLfloat z = +75.0f;

    GLfloat gauge_vertices[] = {
        -gauge_x_range, -gauge_y_pos, z,    // x-gauge line
        +gauge_x_range, -gauge_y_pos, z,

        // express a quad as two triangles:
        // ABCD -> ABC + CDA

        -gauge_x_range, -gauge_y_pos - gauge_x_thickness, z,    // x-gauge triangles
        -gauge_x_range, -gauge_y_pos + gauge_x_thickness, z,
        gauge_x_value - gauge_x_range, -gauge_y_pos + gauge_x_thickness, z,
        gauge_x_value - gauge_x_range, -gauge_y_pos + gauge_x_thickness, z,
        gauge_x_value - gauge_x_range, -gauge_y_pos - gauge_x_thickness, z,
        -gauge_x_range, -gauge_y_pos - gauge_x_thickness, z
    };

    spRewindGauge * p = programRewindCauge;

    p->bind();

    p->setMatrix(pmvMatrix);

    p->enableVertexArray();
    p->setVertexArray(gauge_vertices);

    glDrawArrays(GL_LINES, 0, 2);
    glDrawArrays(GL_TRIANGLES, 2, 6);

    p->disableVertexArray();

    p->release();
}

#endif  // SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE
#endif  // SMASHMINER_CONFIG_ENABLE_CONTROL_GAUGES

/*! Calculate \a light and \a material vector for given light effect to be used with GLSL shader
 * program. 
 *
 * \param[in] lightVector Light vector (see GameWidget::lightAmbient or GameWidget::lightDiffuse with 
 *            values set in GameWidget::GLes2Initialize())
 * \param[in] materialColour colour data with red, green, blue and alpha values.
 * \param[out] lightMaterialVector light and material vector for GLSL shader program
 */
void GameWidget::GLes2MakeLightMaterialData(QVector4D &lightVector, GLfloat *materialColour, 
                                            QVector4D &lightMaterialVector)
{
    lightMaterialVector.setX(lightVector.x() * materialColour[0]);
    lightMaterialVector.setY(lightVector.y() * materialColour[1]);
    lightMaterialVector.setZ(lightVector.z() * materialColour[2]);
    lightMaterialVector.setW(lightVector.w() * materialColour[3]);
}

/// Cleans up OpenGL ES 2 objects
void GameWidget::GLes2Cleanup()
{
    delete programBackground;
    delete programBackgroundEffect2D;

    delete programParticles;
    delete programParticlesEffect2D;

    delete programConstantMaterial;
    delete programConstanMaterialEffect3D;

    delete programConstantMaterialSpecular;
    delete programConstantMaterialSpecularEffect3D;

    delete programVariableMaterial;
    delete programVariableMaterial3D;

#ifdef SMASHMINER_CONFIG_ENABLE_CONTROL_GAUGES
    delete programControlCauges;
#ifdef SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE
    delete programRewindCauge;
#endif  // SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE
#endif  // SMASHMINER_CONFIG_ENABLE_CONTROL_GAUGES

    delete programTexture;
    delete programTextureEffects3D;

    // delete vertex shaders.

    delete vertexShaderSimple;

    delete vertexShaderBackground;
    delete vertexShaderBackgroundEffect2D;

    delete vertexShaderParticles;
    delete vertexShaderParticlesEffect2D;

    delete vertexShaderConstantMaterial;
    delete vertexShaderConstantMaterialEffect3D;

    delete vertexShaderConstantMaterialSpecular;
    delete vertexShaderConstantMaterialSpecularEffect3D;

    delete vertexShaderVariableMaterial;
    delete vertexShaderVariableMaterialEffect3D;

    delete vertexShaderTexture;
    delete vertexShaderTextureEffect3D;

    // delete fragment shaders.

    delete fragmentShaderRedOnly;
    delete fragmentShaderGreenOnly;
    delete fragmentShaderBlueOnly;

    delete fragmentShaderBackGround;
    delete fragmentShaderBackGroundEffect;

    delete fragmentShaderParticles;

    delete fragmentShaderTexture;

    delete fragmentShaderMaterial;

    // delete textures.
    //^^^^^^^^^^^^^^^^^^

    glDeleteTextures(NumberOfTextures, textureNames);
}

/// Gets ambient light material data by GameObject type
void GameWidget::ambientLmDataByType(const GameObject *a, QVector4D &vector)
{
    switch (a->objectType()) {
        case TypeCollectibleMineralA:
            vector = this->mineralALightsAmbientLmData;
        break;
        case TypeCollectibleMineralB:
            vector = this->mineralBLightsAmbientLmData;
        break;
        case TypeCollectibleMineralC:
            vector = this->mineralCLightsAmbientLmData;
        break;
        case TypeCollectibleMineralD:
            vector = this->mineralDLightsAmbientLmData;
        break;
         default:
            qFatal("No ambient light data for game object type %d", a->objectType());
    };
}

/// Gets diffuse light material data by GameObject type
void GameWidget::diffuseLmDataByType(const GameObject *a, QVector4D &vector)
{
    switch(a->objectType()) {
        case TypeCollectibleMineralA:
            vector = this->mineralALightsDiffuseLmData;
        break;
        case TypeCollectibleMineralB:
            vector = this->mineralBLightsDiffuseLmData;
        break;
        case TypeCollectibleMineralC:
            vector = this->mineralCLightsDiffuseLmData;
        break;
        case TypeCollectibleMineralD:
            vector = this->mineralDLightsDiffuseLmData;
        break;
        default:
            qFatal("No diffuse light data for game object type %d", a->objectType());
    };
}

/*! Compiles the OpenGL ES 2 shader programs
 *
 * \param shader    QGLShader class instance for managing shader program.
 * \param program   Shader program source code text.
 * \name            Name of the shader program for providing info if compilation fails. 
 */
void GameWidget::GLes2CompileGLSL(QGLShader *shader, const char *program, const char *name)
{
    shader->compileSourceCode(program);
    if (!shader->isCompiled())
        qFatal("Problem compiling shader program '%s': %s", name, shader->log().toUtf8().data());
}

#endif  // SMASHMINER_CONFIG_USE_OPENGL_ES_2
